// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

#include "meta-service/meta_service.h"

#include <brpc/controller.h>
#include <bvar/window.h>
#include <fmt/core.h>
#include <gen_cpp/cloud.pb.h>
#include <gen_cpp/olap_file.pb.h>
#include <google/protobuf/repeated_field.h>
#include <gtest/gtest.h>

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <memory>
#include <random>
#include <string>
#include <thread>

#include "common/config.h"
#include "common/logging.h"
#include "common/util.h"
#include "cpp/sync_point.h"
#include "meta-service/meta_service_helper.h"
#include "meta-store/blob_message.h"
#include "meta-store/document_message.h"
#include "meta-store/keys.h"
#include "meta-store/mem_txn_kv.h"
#include "meta-store/txn_kv.h"
#include "meta-store/txn_kv_error.h"
#include "meta-store/versioned_value.h"
#include "mock_resource_manager.h"
#include "rate-limiter/rate_limiter.h"
#include "resource-manager/resource_manager.h"

int main(int argc, char** argv) {
    const std::string conf_file = "doris_cloud.conf";
    if (!doris::cloud::config::init(conf_file.c_str(), true)) {
        std::cerr << "failed to init config file, conf=" << conf_file << std::endl;
        return -1;
    }

    config::enable_retry_txn_conflict = false;
    config::enable_txn_store_retry = true;
    config::txn_store_retry_base_intervals_ms = 1;
    config::txn_store_retry_times = 20;
    config::enable_check_instance_id = false;

    if (!doris::cloud::init_glog("meta_service_test")) {
        std::cerr << "failed to init glog" << std::endl;
        return -1;
    }
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

namespace doris::cloud {

std::unique_ptr<MetaServiceProxy> get_meta_service(bool mock_resource_mgr) {
    int ret = 0;
    // MemKv
    auto txn_kv = std::dynamic_pointer_cast<TxnKv>(std::make_shared<MemTxnKv>());
    if (txn_kv != nullptr) {
        ret = txn_kv->init();
        [&] { ASSERT_EQ(ret, 0); }();
    }
    [&] { ASSERT_NE(txn_kv.get(), nullptr); }();

    // FdbKv
    //     config::fdb_cluster_file_path = "fdb.cluster";
    //     static auto txn_kv = std::dynamic_pointer_cast<TxnKv>(std::make_shared<FdbTxnKv>());
    //     static std::atomic<bool> init {false};
    //     bool tmp = false;
    //     if (init.compare_exchange_strong(tmp, true)) {
    //         int ret = txn_kv->init();
    //         [&] { ASSERT_EQ(ret, 0); ASSERT_NE(txn_kv.get(), nullptr); }();
    //     }

    std::unique_ptr<Transaction> txn;
    EXPECT_EQ(txn_kv->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->remove("\x00", "\xfe"); // This is dangerous if the fdb is not correctly set
    EXPECT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

    auto rs = mock_resource_mgr ? std::make_shared<MockResourceManager>(txn_kv)
                                : std::make_shared<ResourceManager>(txn_kv);
    auto rl = std::make_shared<RateLimiter>();
    auto snapshot = std::make_shared<SnapshotManager>(txn_kv);
    auto meta_service = std::make_unique<MetaServiceImpl>(txn_kv, rs, rl, snapshot);
    return std::make_unique<MetaServiceProxy>(std::move(meta_service));
}

std::unique_ptr<MetaServiceProxy> get_meta_service() {
    return get_meta_service(true);
}

std::unique_ptr<MetaServiceProxy> get_fdb_meta_service() {
    config::fdb_cluster_file_path = "fdb.cluster";
    static auto txn_kv = std::dynamic_pointer_cast<TxnKv>(std::make_shared<FdbTxnKv>());
    static std::atomic<bool> init {false};
    bool tmp = false;
    if (init.compare_exchange_strong(tmp, true)) {
        int ret = txn_kv->init();
        [&] {
            ASSERT_EQ(ret, 0);
            ASSERT_NE(txn_kv.get(), nullptr);
        }();
    }
    auto rs = std::make_shared<MockResourceManager>(txn_kv);
    auto rl = std::make_shared<RateLimiter>();
    auto snapshot = std::make_shared<SnapshotManager>(txn_kv);
    auto meta_service = std::make_unique<MetaServiceImpl>(txn_kv, rs, rl, snapshot);
    return std::make_unique<MetaServiceProxy>(std::move(meta_service));
}

static std::string next_rowset_id() {
    static int cnt = 0;
    return std::to_string(++cnt);
}

void add_tablet(CreateTabletsRequest& req, int64_t table_id, int64_t index_id, int64_t partition_id,
                int64_t tablet_id) {
    auto tablet = req.add_tablet_metas();
    tablet->set_table_id(table_id);
    tablet->set_index_id(index_id);
    tablet->set_partition_id(partition_id);
    tablet->set_tablet_id(tablet_id);
    auto schema = tablet->mutable_schema();
    schema->set_schema_version(0);
    auto first_rowset = tablet->add_rs_metas();
    first_rowset->set_rowset_id(0); // required
    first_rowset->set_rowset_id_v2(next_rowset_id());
    first_rowset->set_start_version(0);
    first_rowset->set_end_version(1);
    first_rowset->mutable_tablet_schema()->CopyFrom(*schema);
}

void create_tablet(MetaServiceProxy* meta_service, int64_t table_id, int64_t index_id,
                   int64_t partition_id, int64_t tablet_id) {
    brpc::Controller cntl;
    CreateTabletsRequest req;
    CreateTabletsResponse res;
    req.set_db_id(1); // default db_id
    add_tablet(req, table_id, index_id, partition_id, tablet_id);
    meta_service->create_tablets(&cntl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << tablet_id;
}

static void create_tablet_with_db_id(MetaServiceProxy* meta_service, int64_t db_id,
                                     int64_t table_id, int64_t index_id, int64_t partition_id,
                                     int64_t tablet_id) {
    brpc::Controller cntl;
    CreateTabletsRequest req;
    CreateTabletsResponse res;
    req.set_db_id(db_id);
    add_tablet(req, table_id, index_id, partition_id, tablet_id);
    meta_service->create_tablets(&cntl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << tablet_id;
}

static void begin_txn(MetaServiceProxy* meta_service, int64_t db_id, const std::string& label,
                      int64_t table_id, int64_t& txn_id) {
    brpc::Controller cntl;
    BeginTxnRequest req;
    BeginTxnResponse res;
    auto txn_info = req.mutable_txn_info();
    txn_info->set_db_id(db_id);
    txn_info->set_label(label);
    txn_info->add_table_ids(table_id);
    txn_info->set_timeout_ms(36000);
    meta_service->begin_txn(&cntl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;
    ASSERT_TRUE(res.has_txn_id()) << label;
    txn_id = res.txn_id();
}

static void commit_txn(MetaServiceProxy* meta_service, int64_t db_id, int64_t txn_id,
                       const std::string& label) {
    brpc::Controller cntl;
    CommitTxnRequest req;
    CommitTxnResponse res;
    req.set_db_id(db_id);
    req.set_txn_id(txn_id);
    meta_service->commit_txn(&cntl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK)
            << label << ", res=" << res.ShortDebugString();
}

doris::RowsetMetaCloudPB create_rowset(int64_t txn_id, int64_t tablet_id, int partition_id = 10,
                                       int64_t version = -1, int num_rows = 100) {
    doris::RowsetMetaCloudPB rowset;
    rowset.set_rowset_id(0); // required
    rowset.set_rowset_id_v2(next_rowset_id());
    rowset.set_tablet_id(tablet_id);
    rowset.set_partition_id(partition_id);
    rowset.set_txn_id(txn_id);
    if (version > 0) {
        rowset.set_start_version(version);
        rowset.set_end_version(version);
    }
    rowset.set_num_segments(1);
    rowset.set_num_rows(num_rows);
    rowset.set_data_disk_size(num_rows * 100);
    rowset.set_index_disk_size(num_rows * 10);
    rowset.set_total_disk_size(num_rows * 110);
    rowset.mutable_tablet_schema()->set_schema_version(0);
    rowset.set_txn_expiration(::time(nullptr)); // Required by DCHECK
    return rowset;
}

static void prepare_rowset(MetaServiceProxy* meta_service, const doris::RowsetMetaCloudPB& rowset,
                           CreateRowsetResponse& res) {
    brpc::Controller cntl;
    auto arena = res.GetArena();
    auto req = google::protobuf::Arena::CreateMessage<CreateRowsetRequest>(arena);
    req->mutable_rowset_meta()->CopyFrom(rowset);
    meta_service->prepare_rowset(&cntl, req, &res, nullptr);
    if (!arena) delete req;
}

void commit_rowset(MetaServiceProxy* meta_service, const doris::RowsetMetaCloudPB& rowset,
                   CreateRowsetResponse& res) {
    brpc::Controller cntl;
    auto arena = res.GetArena();
    auto req = google::protobuf::Arena::CreateMessage<CreateRowsetRequest>(arena);
    req->mutable_rowset_meta()->CopyFrom(rowset);
    meta_service->commit_rowset(&cntl, req, &res, nullptr);
    if (!arena) delete req;
}

static void update_tmp_rowset(MetaServiceProxy* meta_service,
                              const doris::RowsetMetaCloudPB& rowset, CreateRowsetResponse& res) {
    brpc::Controller cntl;
    auto arena = res.GetArena();
    auto req = google::protobuf::Arena::CreateMessage<CreateRowsetRequest>(arena);
    req->mutable_rowset_meta()->CopyFrom(rowset);
    meta_service->update_tmp_rowset(&cntl, req, &res, nullptr);
    if (!arena) delete req;
}

static void get_delete_bitmap_update_lock(MetaServiceProxy* meta_service,
                                          GetDeleteBitmapUpdateLockResponse& res, int64_t db_id,
                                          int64_t table_id, int64_t index_id,
                                          const std::vector<std::array<int64_t, 2>>& tablet_idxes,
                                          int64_t expiration, int64_t lock_id, int64_t initiator,
                                          bool require_compaction_stats) {
    brpc::Controller cntl;
    GetDeleteBitmapUpdateLockRequest req;
    req.set_cloud_unique_id("test_cloud_unique_id");
    req.set_table_id(table_id);
    for (const auto& [partition_id, _] : tablet_idxes) {
        req.add_partition_ids(partition_id);
    }
    req.set_expiration(expiration);
    req.set_lock_id(lock_id);
    req.set_initiator(initiator);
    req.set_require_compaction_stats(require_compaction_stats);
    for (const auto& [partition_id, tablet_id] : tablet_idxes) {
        TabletIndexPB* idx = req.add_tablet_indexes();
        idx->set_db_id(db_id);
        idx->set_table_id(table_id);
        idx->set_index_id(index_id);
        idx->set_partition_id(partition_id);
        idx->set_tablet_id(tablet_id);
    }
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
}

void insert_rowset(MetaServiceProxy* meta_service, int64_t db_id, const std::string& label,
                   int64_t table_id, int64_t partition_id, int64_t tablet_id) {
    int64_t txn_id = 0;
    ASSERT_NO_FATAL_FAILURE(begin_txn(meta_service, db_id, label, table_id, txn_id));
    CreateRowsetResponse res;
    auto rowset = create_rowset(txn_id, tablet_id, partition_id);
    prepare_rowset(meta_service, rowset, res);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;
    res.Clear();
    ASSERT_NO_FATAL_FAILURE(commit_rowset(meta_service, rowset, res));
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;
    commit_txn(meta_service, db_id, txn_id, label);
}

void insert_rowsets(MetaServiceProxy* meta_service, int64_t db_id, const std::string& label,
                    int64_t table_id, int64_t partition_id, std::vector<int64_t> tablet_ids) {
    int64_t txn_id = 0;
    ASSERT_NO_FATAL_FAILURE(begin_txn(meta_service, db_id, label, table_id, txn_id));
    for (auto tablet_id : tablet_ids) {
        CreateRowsetResponse res;
        auto rowset = create_rowset(txn_id, tablet_id, partition_id);
        prepare_rowset(meta_service, rowset, res);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;
        res.Clear();
        ASSERT_NO_FATAL_FAILURE(commit_rowset(meta_service, rowset, res));
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;
    }
    commit_txn(meta_service, db_id, txn_id, label);
}

static void add_tablet_metas(MetaServiceProxy* meta_service, std::string instance_id,
                             int64_t table_id, int64_t index_id,
                             const std::vector<std::array<int64_t, 2>>& tablet_idxes,
                             bool skip_tablet_meta = false) {
    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);

    for (const auto& idx : tablet_idxes) {
        int64_t partition_id = idx[0];
        int64_t tablet_id = idx[1];
        std::string stats_key =
                stats_tablet_key({instance_id, table_id, index_id, partition_id, tablet_id});
        TabletStatsPB stats;
        stats.set_base_compaction_cnt(10);
        stats.set_cumulative_compaction_cnt(20);
        stats.set_cumulative_point(30);
        txn->put(stats_key, stats.SerializeAsString());

        if (skip_tablet_meta) {
            continue;
        }
        doris::TabletMetaCloudPB tablet_pb;
        tablet_pb.set_table_id(table_id);
        tablet_pb.set_index_id(index_id);
        tablet_pb.set_partition_id(partition_id);
        tablet_pb.set_tablet_id(tablet_id);
        tablet_pb.set_tablet_state(doris::TabletStatePB::PB_RUNNING);
        auto tablet_meta_key =
                meta_tablet_key({instance_id, table_id, index_id, partition_id, tablet_id});
        auto tablet_meta_val = tablet_pb.SerializeAsString();
        txn->put(tablet_meta_key, tablet_meta_val);
    }
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
}

static void start_compaction_job(MetaService* meta_service, int64_t tablet_id,
                                 const std::string& job_id, const std::string& initiator,
                                 int base_compaction_cnt, int cumu_compaction_cnt,
                                 TabletCompactionJobPB::CompactionType type,
                                 StartTabletJobResponse& res,
                                 std::pair<int64_t, int64_t> input_version = {0, 0}) {
    brpc::Controller cntl;
    StartTabletJobRequest req;
    req.mutable_job()->mutable_idx()->set_tablet_id(tablet_id);
    auto compaction = req.mutable_job()->add_compaction();
    compaction->set_id(job_id);
    compaction->set_initiator(initiator);
    compaction->set_base_compaction_cnt(base_compaction_cnt);
    compaction->set_cumulative_compaction_cnt(cumu_compaction_cnt);
    compaction->set_type(type);
    long now = time(nullptr);
    compaction->set_expiration(now + 12);
    compaction->set_lease(now + 3);
    if (input_version.second > 0) {
        compaction->add_input_versions(input_version.first);
        compaction->add_input_versions(input_version.second);
        compaction->set_check_input_versions_range(true);
    }
    meta_service->start_tablet_job(&cntl, &req, &res, nullptr);
};

TEST(MetaServiceTest, GetInstanceIdTest) {
    extern std::string get_instance_id(const std::shared_ptr<ResourceManager>& rc_mgr,
                                       const std::string& cloud_unique_id);
    auto meta_service = get_meta_service();
    auto sp = SyncPoint::get_instance();
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
    };
    sp->set_call_back("get_instance_id_err", [&](auto&& args) {
        std::string* err = try_any_cast<std::string*>(args[0]);
        *err = "can't find node from cache";
    });
    sp->enable_processing();

    auto instance_id =
            get_instance_id(meta_service->resource_mgr(), "1:ALBJLH4Q:m-n3qdpyal27rh8iprxx");
    ASSERT_EQ(instance_id, "ALBJLH4Q");

    // version not support
    instance_id = get_instance_id(meta_service->resource_mgr(), "2:ALBJLH4Q:m-n3qdpyal27rh8iprxx");
    ASSERT_EQ(instance_id, "");

    // degraded format err
    instance_id = get_instance_id(meta_service->resource_mgr(), "1:ALBJLH4Q");
    ASSERT_EQ(instance_id, "");

    // std::invalid_argument
    instance_id = get_instance_id(meta_service->resource_mgr(),
                                  "invalid_version:ALBJLH4Q:m-n3qdpyal27rh8iprxx");
    ASSERT_EQ(instance_id, "");

    // std::out_of_range
    instance_id = get_instance_id(meta_service->resource_mgr(),
                                  "12345678901:ALBJLH4Q:m-n3qdpyal27rh8iprxx");
    ASSERT_EQ(instance_id, "");

    config::enable_check_instance_id = true;
    auto ms = get_meta_service(false);
    instance_id =
            get_instance_id(ms->resource_mgr(), "1:ALBJLH4Q-check-invalid:m-n3qdpyal27rh8iprxx");
    ASSERT_EQ(instance_id, "");

    sp->set_call_back("is_instance_id_registered", [&](auto&& args) {
        TxnErrorCode* c0 = try_any_cast<TxnErrorCode*>(args[0]);
        *c0 = TxnErrorCode::TXN_OK;
    });
    instance_id =
            get_instance_id(ms->resource_mgr(), "1:ALBJLH4Q-check-invalid:m-n3qdpyal27rh8iprxx");
    ASSERT_EQ(instance_id, "ALBJLH4Q-check-invalid");
    config::enable_check_instance_id = false;

    sp->clear_all_call_backs();
    sp->clear_trace();
    sp->disable_processing();
}

TEST(MetaServiceTest, CreateInstanceTest) {
    auto meta_service = get_meta_service();

    // case: normal create instance with obj info
    {
        brpc::Controller cntl;
        CreateInstanceRequest req;
        req.set_instance_id("test_instance");
        req.set_user_id("test_user");
        req.set_name("test_name");
        ObjectStoreInfoPB obj;
        obj.set_ak("123");
        obj.set_sk("321");
        obj.set_bucket("456");
        obj.set_prefix("654");
        obj.set_endpoint("789");
        obj.set_region("987");
        obj.set_external_endpoint("888");
        obj.set_provider(ObjectStoreInfoPB::BOS);
        req.mutable_obj_info()->CopyFrom(obj);

        auto sp = SyncPoint::get_instance();
        sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
            auto* ret = try_any_cast<int*>(args[0]);
            *ret = 0;
            auto* key = try_any_cast<std::string*>(args[1]);
            *key = "test";
            auto* key_id = try_any_cast<int64_t*>(args[2]);
            *key_id = 1;
        });
        sp->enable_processing();
        CreateInstanceResponse res;
        meta_service->create_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                      &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        sp->clear_all_call_backs();
        sp->clear_trace();
        sp->disable_processing();
    }

    // case: normal create instance without obj info and hdfs info
    {
        brpc::Controller cntl;
        CreateInstanceRequest req;
        req.set_instance_id("test_instance_without_hdfs_and_obj");
        req.set_user_id("test_user");
        req.set_name("test_name");

        auto sp = SyncPoint::get_instance();
        sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
            auto* ret = try_any_cast<int*>(args[0]);
            *ret = 0;
            auto* key = try_any_cast<std::string*>(args[1]);
            *key = "test";
            auto* key_id = try_any_cast<int64_t*>(args[2]);
            *key_id = 1;
        });
        sp->enable_processing();
        CreateInstanceResponse res;
        meta_service->create_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                      &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        sp->clear_all_call_backs();
        sp->clear_trace();
        sp->disable_processing();
    }

    // case: normal create instance with hdfs info
    {
        brpc::Controller cntl;
        CreateInstanceRequest req;
        req.set_instance_id("test_instance_with_hdfs_info");
        req.set_user_id("test_user");
        req.set_name("test_name");
        HdfsVaultInfo hdfs;
        HdfsBuildConf conf;
        conf.set_fs_name("hdfs://127.0.0.1:8020");
        conf.set_user("test_user");
        hdfs.mutable_build_conf()->CopyFrom(conf);
        StorageVaultPB vault;
        vault.mutable_hdfs_info()->CopyFrom(hdfs);
        req.mutable_vault()->CopyFrom(vault);

        auto sp = SyncPoint::get_instance();
        sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
            auto* ret = try_any_cast<int*>(args[0]);
            *ret = 0;
            auto* key = try_any_cast<std::string*>(args[1]);
            *key = "test";
            auto* key_id = try_any_cast<int64_t*>(args[2]);
            *key_id = 1;
        });
        sp->enable_processing();
        CreateInstanceResponse res;
        meta_service->create_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                      &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        sp->clear_all_call_backs();
        sp->clear_trace();
        sp->disable_processing();
    }

    // case: request has invalid argument
    {
        brpc::Controller cntl;
        CreateInstanceRequest req;
        CreateInstanceResponse res;
        meta_service->create_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                      &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    }

    // case: normal drop instance
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::DROP);
        req.set_instance_id("test_instance");
        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        InstanceKeyInfo key_info {"test_instance"};
        std::string key;
        std::string val;
        instance_key(key_info, &key);
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        InstanceInfoPB instance;
        instance.ParseFromString(val);
        ASSERT_EQ(instance.status(), InstanceInfoPB::DELETED);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // case: normal refresh instance
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::REFRESH);
        req.set_instance_id("test_instance");
        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // case: rpc get instance
    {
        brpc::Controller cntl;
        GetInstanceRequest req;
        GetInstanceResponse res;
        meta_service->get_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                   &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        req.set_cloud_unique_id("1:test_instance:m-n3qdpyal27rh8iprxx");
        meta_service->get_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                   &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }
}

TEST(MetaServiceTest, AlterS3StorageVaultTest) {
    auto meta_service = get_meta_service();

    auto sp = SyncPoint::get_instance();
    sp->enable_processing();
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });
    std::pair<std::string, std::string> pair;
    sp->set_call_back("extract_object_storage_info:get_aksk_pair", [&](auto&& args) {
        auto* ret = try_any_cast<std::pair<std::string, std::string>*>(args[0]);
        pair = *ret;
    });

    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    std::string key;
    std::string val;
    InstanceKeyInfo key_info {"test_instance"};
    instance_key(key_info, &key);

    ObjectStoreInfoPB obj_info;
    obj_info.set_id("1");
    obj_info.set_ak("ak");
    obj_info.set_sk("sk");
    StorageVaultPB vault;
    constexpr char vault_name[] = "test_alter_s3_vault";
    vault.mutable_obj_info()->MergeFrom(obj_info);
    vault.set_name(vault_name);
    vault.set_id("2");
    InstanceInfoPB instance;
    instance.add_storage_vault_names(vault.name());
    instance.add_resource_ids(vault.id());
    instance.set_instance_id("GetObjStoreInfoTestInstance");
    val = instance.SerializeAsString();
    txn->put(key, val);
    txn->put(storage_vault_key({instance.instance_id(), "2"}), vault.SerializeAsString());
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    txn = nullptr;

    auto get_test_instance = [&](InstanceInfoPB& i) {
        std::string key;
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        InstanceKeyInfo key_info {"test_instance"};
        instance_key(key_info, &key);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ALTER_S3_VAULT);
        StorageVaultPB vault;
        vault.mutable_obj_info()->set_ak("new_ak");
        vault.set_name(vault_name);
        req.mutable_vault()->CopyFrom(vault);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_NE(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        InstanceInfoPB instance;
        get_test_instance(instance);

        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        std::string val;
        ASSERT_EQ(txn->get(storage_vault_key({instance.instance_id(), "2"}), &val),
                  TxnErrorCode::TXN_OK);
        StorageVaultPB get_obj;
        get_obj.ParseFromString(val);
        ASSERT_EQ(get_obj.obj_info().ak(), "ak") << get_obj.obj_info().ak();
    }

    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ALTER_S3_VAULT);
        StorageVaultPB vault;
        ObjectStoreInfoPB obj;
        obj_info.set_ak("new_ak");
        vault.mutable_obj_info()->MergeFrom(obj);
        vault.set_name("test_alter_s3_vault_non_exist");
        req.mutable_vault()->CopyFrom(vault);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_NE(res.status().code(), MetaServiceCode::OK) << res.status().msg();
    }

    {
        AlterObjStoreInfoRequest req;
        constexpr char new_vault_name[] = "@!#vault_name";
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ALTER_S3_VAULT);
        StorageVaultPB vault;
        vault.set_alter_name(new_vault_name);
        ObjectStoreInfoPB obj;
        obj_info.set_ak("new_ak");
        vault.mutable_obj_info()->MergeFrom(obj);
        vault.set_name(vault_name);
        req.mutable_vault()->CopyFrom(vault);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_NE(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        ASSERT_TRUE(res.status().msg().find("invalid storage vault name") != std::string::npos)
                << res.status().msg();
    }

    {
        AlterObjStoreInfoRequest req;
        constexpr char new_vault_name[] = "new_test_alter_s3_vault";
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ALTER_S3_VAULT);
        StorageVaultPB vault;
        vault.set_alter_name(new_vault_name);
        ObjectStoreInfoPB obj;
        obj_info.set_ak("new_ak");
        obj_info.set_sk("new_sk");
        vault.mutable_obj_info()->MergeFrom(obj);
        vault.set_name(vault_name);
        req.mutable_vault()->CopyFrom(vault);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();

        {
            AlterObjStoreInfoRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_op(AlterObjStoreInfoRequest::ALTER_S3_VAULT);
            StorageVaultPB vault;
            vault.set_alter_name(new_vault_name);
            ObjectStoreInfoPB obj;
            obj_info.set_ak("new_ak");
            obj_info.set_sk("new_sk");
            vault.mutable_obj_info()->MergeFrom(obj);
            vault.set_name(new_vault_name);
            req.mutable_vault()->CopyFrom(vault);
            meta_service->alter_storage_vault(
                    reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res,
                    nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::ALREADY_EXISTED) << res.status().msg();
        }

        InstanceInfoPB instance;
        get_test_instance(instance);

        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        std::string val;
        ASSERT_EQ(txn->get(storage_vault_key({instance.instance_id(), "2"}), &val),
                  TxnErrorCode::TXN_OK);
        StorageVaultPB get_obj;
        get_obj.ParseFromString(val);
        ASSERT_EQ(get_obj.name(), new_vault_name) << get_obj.obj_info().ak();
    }

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, AlterHdfsStorageVaultTest) {
    auto meta_service = get_meta_service();

    auto sp = SyncPoint::get_instance();
    sp->enable_processing();
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });
    std::pair<std::string, std::string> pair;
    sp->set_call_back("extract_object_storage_info:get_aksk_pair", [&](auto&& args) {
        auto* ret = try_any_cast<std::pair<std::string, std::string>*>(args[0]);
        pair = *ret;
    });

    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    std::string key;
    std::string val;
    InstanceKeyInfo key_info {"test_instance"};
    instance_key(key_info, &key);

    HdfsBuildConf hdfs_build_conf;
    hdfs_build_conf.set_fs_name("fs_name");
    hdfs_build_conf.set_user("root");
    HdfsVaultInfo hdfs_info;
    hdfs_info.set_prefix("root_path");
    hdfs_info.mutable_build_conf()->MergeFrom(hdfs_build_conf);
    StorageVaultPB vault;
    constexpr char vault_name[] = "test_alter_hdfs_vault";
    vault.mutable_hdfs_info()->MergeFrom(hdfs_info);
    vault.set_name(vault_name);
    vault.set_id("2");
    InstanceInfoPB instance;
    instance.add_storage_vault_names(vault.name());
    instance.add_resource_ids(vault.id());
    instance.set_instance_id("GetObjStoreInfoTestInstance");
    val = instance.SerializeAsString();
    txn->put(key, val);
    txn->put(storage_vault_key({instance.instance_id(), "2"}), vault.SerializeAsString());
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    txn = nullptr;

    auto get_test_instance = [&](InstanceInfoPB& i) {
        std::string key;
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        InstanceKeyInfo key_info {"test_instance"};
        instance_key(key_info, &key);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ALTER_HDFS_VAULT);
        StorageVaultPB vault;
        vault.mutable_hdfs_info()->mutable_build_conf()->set_user("hadoop");
        vault.set_name(vault_name);
        req.mutable_vault()->CopyFrom(vault);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();

        InstanceInfoPB instance;
        get_test_instance(instance);

        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        std::string val;
        ASSERT_EQ(txn->get(storage_vault_key({instance.instance_id(), "2"}), &val),
                  TxnErrorCode::TXN_OK);
        StorageVaultPB get_obj;
        get_obj.ParseFromString(val);
        ASSERT_EQ(get_obj.hdfs_info().build_conf().user(), "hadoop")
                << get_obj.hdfs_info().build_conf().fs_name();
    }

    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ALTER_HDFS_VAULT);
        StorageVaultPB vault;
        auto* hdfs = vault.mutable_hdfs_info();
        hdfs->set_prefix("fake_one");
        vault.set_name("test_alter_hdfs_vault_non_exist");
        req.mutable_vault()->CopyFrom(vault);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_NE(res.status().code(), MetaServiceCode::OK) << res.status().msg();
    }

    {
        AlterObjStoreInfoRequest req;
        constexpr char new_vault_name[] = "Thi213***@fakeVault";
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ALTER_HDFS_VAULT);
        StorageVaultPB vault;
        vault.mutable_hdfs_info()->mutable_build_conf()->set_user("hadoop");
        vault.set_name(vault_name);
        vault.set_alter_name(new_vault_name);
        req.mutable_vault()->CopyFrom(vault);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_NE(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        ASSERT_TRUE(res.status().msg().find("invalid storage vault name") != std::string::npos)
                << res.status().msg();
    }

    {
        AlterObjStoreInfoRequest req;
        constexpr char new_vault_name[] = "new_test_alter_hdfs_vault";
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ALTER_HDFS_VAULT);
        StorageVaultPB vault;
        vault.mutable_hdfs_info()->mutable_build_conf()->set_user("hadoop");
        vault.set_name(vault_name);
        vault.set_alter_name(new_vault_name);
        req.mutable_vault()->CopyFrom(vault);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();

        {
            AlterObjStoreInfoRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_op(AlterObjStoreInfoRequest::ALTER_HDFS_VAULT);
            StorageVaultPB vault;
            vault.mutable_hdfs_info()->mutable_build_conf()->set_user("hadoop");
            vault.set_name(new_vault_name);
            vault.set_alter_name(new_vault_name);
            req.mutable_vault()->CopyFrom(vault);

            brpc::Controller cntl;
            AlterObjStoreInfoResponse res;
            meta_service->alter_storage_vault(
                    reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res,
                    nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::ALREADY_EXISTED) << res.status().msg();
        }

        InstanceInfoPB instance;
        get_test_instance(instance);

        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        std::string val;
        ASSERT_EQ(txn->get(storage_vault_key({instance.instance_id(), "2"}), &val),
                  TxnErrorCode::TXN_OK);
        StorageVaultPB get_obj;
        get_obj.ParseFromString(val);
        ASSERT_EQ(get_obj.name(), new_vault_name) << get_obj.obj_info().ak();
    }

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, AlterClusterTest) {
    auto meta_service = get_meta_service();
    ASSERT_NE(meta_service, nullptr);

    // case: normal add cluster
    {
        brpc::Controller cntl;
        AlterClusterRequest req;
        req.set_instance_id(mock_instance);
        req.mutable_cluster()->set_cluster_name(mock_cluster_name);
        req.set_op(AlterClusterRequest::ADD_CLUSTER);
        AlterClusterResponse res;
        meta_service->alter_cluster(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // case: request has invalid argument
    {
        brpc::Controller cntl;
        AlterClusterRequest req;
        req.set_op(AlterClusterRequest::DROP_CLUSTER);
        AlterClusterResponse res;
        meta_service->alter_cluster(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    }

    // add node
    {
        brpc::Controller cntl;
        AlterClusterRequest req;
        req.set_instance_id(mock_instance);
        req.set_op(AlterClusterRequest::ADD_NODE);
        req.mutable_cluster()->set_cluster_name(mock_cluster_name);
        req.mutable_cluster()->set_cluster_id(mock_cluster_id);
        req.mutable_cluster()->set_type(ClusterPB::COMPUTE);
        auto node = req.mutable_cluster()->add_nodes();
        node->set_ip("127.0.0.1");
        node->set_heartbeat_port(9999);
        AlterClusterResponse res;
        meta_service->alter_cluster(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // drop node
    {
        brpc::Controller cntl;
        AlterClusterRequest req;
        req.set_instance_id(mock_instance);
        req.set_op(AlterClusterRequest::DROP_NODE);
        req.mutable_cluster()->set_cluster_name(mock_cluster_name);
        req.mutable_cluster()->set_cluster_id(mock_cluster_id);
        req.mutable_cluster()->set_type(ClusterPB::COMPUTE);
        auto node = req.mutable_cluster()->add_nodes();
        node->set_ip("127.0.0.1");
        node->set_heartbeat_port(9999);
        AlterClusterResponse res;
        meta_service->alter_cluster(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // rename cluster
    {
        brpc::Controller cntl;
        AlterClusterRequest req;
        req.set_instance_id(mock_instance);
        req.mutable_cluster()->set_cluster_id(mock_cluster_id);
        req.mutable_cluster()->set_cluster_name("rename_cluster_name");
        req.set_op(AlterClusterRequest::RENAME_CLUSTER);
        AlterClusterResponse res;
        meta_service->alter_cluster(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // set cluster status
    {
        brpc::Controller cntl;
        AlterClusterRequest req;
        req.set_instance_id(mock_instance);
        req.mutable_cluster()->set_cluster_id(mock_cluster_id);
        req.mutable_cluster()->set_cluster_status(ClusterStatus::SUSPENDED);
        req.set_op(AlterClusterRequest::SET_CLUSTER_STATUS);
        AlterClusterResponse res;
        meta_service->alter_cluster(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // set UPDATE_CLUSTER_MYSQL_USER_NAME
    {
        brpc::Controller cntl;
        AlterClusterRequest req;
        req.set_instance_id(mock_instance);
        req.mutable_cluster()->set_cluster_id(mock_cluster_id);
        req.mutable_cluster()->add_mysql_user_name("test_user");
        req.set_op(AlterClusterRequest::UPDATE_CLUSTER_MYSQL_USER_NAME);
        AlterClusterResponse res;
        meta_service->alter_cluster(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }
}

TEST(MetaServiceTest, GetClusterTest) {
    auto meta_service = get_meta_service();

    // add cluster first
    InstanceKeyInfo key_info {mock_instance};
    std::string key;
    std::string val;
    instance_key(key_info, &key);

    InstanceInfoPB instance;
    instance.set_instance_id(mock_instance);
    ClusterPB c1;
    c1.set_cluster_name(mock_cluster_name);
    c1.set_cluster_id(mock_cluster_id);
    c1.add_mysql_user_name()->append("m1");
    instance.add_clusters()->CopyFrom(c1);
    val = instance.SerializeAsString();

    std::unique_ptr<Transaction> txn;
    std::string get_val;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

    // case: normal get
    {
        brpc::Controller cntl;
        GetClusterRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_cluster_id(mock_cluster_id);
        req.set_cluster_name("test_cluster");
        GetClusterResponse res;
        meta_service->get_cluster(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                  &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }
}

TEST(MetaServiceTest, BeginTxnTest) {
    auto meta_service = get_meta_service();
    int64_t db_id = 666;
    int64_t table_id = 123;
    const std::string& label = "test_label";
    int64_t timeout_ms = 60 * 1000;

    // test invalid argument
    {
        brpc::Controller cntl;
        BeginTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        BeginTxnResponse res;
        meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    }

    {
        brpc::Controller cntl;
        BeginTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");

        TxnInfoPB txn_info;
        txn_info.set_db_id(db_id);
        txn_info.add_table_ids(table_id);
        txn_info.set_timeout_ms(timeout_ms);
        req.mutable_txn_info()->CopyFrom(txn_info);
        BeginTxnResponse res;
        meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    }

    {
        brpc::Controller cntl;
        BeginTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");

        TxnInfoPB txn_info;
        txn_info.set_db_id(db_id);
        txn_info.set_label(label);
        txn_info.add_table_ids(table_id);
        txn_info.set_timeout_ms(timeout_ms);
        req.mutable_txn_info()->CopyFrom(txn_info);
        BeginTxnResponse res;
        meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // case: label already used
    {
        brpc::Controller cntl;
        BeginTxnRequest req;
        auto label_already_in_use = "test_label_already_in_use";

        req.set_cloud_unique_id("test_cloud_unique_id");
        TxnInfoPB txn_info;
        txn_info.set_db_id(888);
        txn_info.set_label(label_already_in_use);
        txn_info.add_table_ids(456);
        txn_info.set_timeout_ms(36000);
        req.mutable_txn_info()->CopyFrom(txn_info);
        BeginTxnResponse res;
        meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

        meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::TXN_LABEL_ALREADY_USED);
        auto found = res.status().msg().find(fmt::format(
                "Label [{}] has already been used, relate to txn", label_already_in_use));
        ASSERT_NE(found, std::string::npos);
    }

    // case: dup begin txn request
    {
        brpc::Controller cntl;
        BeginTxnRequest req;

        req.set_cloud_unique_id("test_cloud_unique_id");
        TxnInfoPB txn_info;
        txn_info.set_db_id(999);
        txn_info.set_label("test_label_dup_request");
        txn_info.add_table_ids(789);
        UniqueIdPB unique_id_pb;
        unique_id_pb.set_hi(100);
        unique_id_pb.set_lo(10);
        txn_info.mutable_request_id()->CopyFrom(unique_id_pb);
        txn_info.set_timeout_ms(36000);
        req.mutable_txn_info()->CopyFrom(txn_info);
        BeginTxnResponse res;
        meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

        meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::TXN_DUPLICATED_REQ);
    }

    {
        // ===========================================================================
        // threads concurrent execution with sequence in begin_txn with same label:
        //
        //      thread1              thread2
        //         |                    |
        //         |                commit_txn1
        //         |                    |
        //         |                    |
        //         |                    |
        //       commit_txn2            |
        //         |                    |
        //         v                    v
        //

        std::mutex go_mutex;
        std::condition_variable go_cv;
        bool go = false;
        auto sp = SyncPoint::get_instance();
        DORIS_CLOUD_DEFER {
            SyncPoint::get_instance()->clear_all_call_backs();
        };

        std::atomic<int32_t> count_txn1 = {0};
        std::atomic<int32_t> count_txn2 = {0};
        std::atomic<int32_t> count_txn3 = {0};

        int64_t db_id = 1928354123;
        int64_t table_id = 12131231231;
        std::string test_label = "test_race_with_same_label";

        std::atomic<int32_t> success_txn = {0};

        sp->set_call_back("begin_txn:before:commit_txn:1", [&](auto&& args) {
            const auto& label = *try_any_cast<std::string*>(args[0]);
            std::unique_lock<std::mutex> _lock(go_mutex);
            count_txn1++;
            LOG(INFO) << "count_txn1:" << count_txn1 << " label=" << label;
            if (count_txn1 == 1) {
                {
                    LOG(INFO) << "count_txn1:" << count_txn1 << " label=" << label << " go=" << go;
                    go_cv.wait(_lock);
                }
            }

            if (count_txn1 == 2) {
                {
                    LOG(INFO) << "count_txn1:" << count_txn1 << " label=" << label << " go=" << go;
                    go_cv.notify_all();
                }
            }
        });

        sp->set_call_back("begin_txn:after:commit_txn:1", [&](auto&& args) {
            const auto& label = *try_any_cast<std::string*>(args[0]);
            std::unique_lock<std::mutex> _lock(go_mutex);
            count_txn2++;
            LOG(INFO) << "count_txn2:" << count_txn2 << " label=" << label;
            if (count_txn2 == 1) {
                {
                    LOG(INFO) << "count_txn2:" << count_txn2 << " label=" << label << " go=" << go;
                    go_cv.wait(_lock);
                }
            }

            if (count_txn2 == 2) {
                {
                    LOG(INFO) << "count_txn2:" << count_txn2 << " label=" << label << " go=" << go;
                    go_cv.notify_all();
                }
            }
        });

        sp->set_call_back("begin_txn:after:commit_txn:2", [&](auto&& args) {
            int64_t txn_id = *try_any_cast<int64_t*>(args[0]);
            count_txn3++;
            LOG(INFO) << "count_txn3:" << count_txn3 << " txn_id=" << txn_id;
        });

        sp->enable_processing();

        std::thread thread1([&] {
            {
                std::unique_lock<std::mutex> _lock(go_mutex);
                go_cv.wait(_lock, [&] { return go; });
            }
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            TxnInfoPB txn_info;
            txn_info.set_db_id(db_id);
            txn_info.set_label(test_label);
            txn_info.add_table_ids(table_id);
            UniqueIdPB unique_id_pb;
            unique_id_pb.set_hi(1001);
            unique_id_pb.set_lo(11);
            txn_info.mutable_request_id()->CopyFrom(unique_id_pb);
            txn_info.set_timeout_ms(36000);
            req.mutable_txn_info()->CopyFrom(txn_info);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            if (res.status().code() == MetaServiceCode::OK) {
                success_txn++;
            } else {
                ASSERT_EQ(res.status().code(), MetaServiceCode::KV_TXN_CONFLICT);
            }
        });

        std::thread thread2([&] {
            {
                std::unique_lock<std::mutex> _lock(go_mutex);
                go_cv.wait(_lock, [&] { return go; });
            }
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            TxnInfoPB txn_info;
            txn_info.set_db_id(db_id);
            txn_info.set_label(test_label);
            txn_info.add_table_ids(table_id);
            UniqueIdPB unique_id_pb;
            unique_id_pb.set_hi(100);
            unique_id_pb.set_lo(10);
            txn_info.mutable_request_id()->CopyFrom(unique_id_pb);
            txn_info.set_timeout_ms(36000);
            req.mutable_txn_info()->CopyFrom(txn_info);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            if (res.status().code() == MetaServiceCode::OK) {
                success_txn++;
            } else {
                ASSERT_EQ(res.status().code(), MetaServiceCode::KV_TXN_CONFLICT);
            }
        });

        std::unique_lock<std::mutex> go_lock(go_mutex);
        go = true;
        go_lock.unlock();
        go_cv.notify_all();

        thread1.join();
        thread2.join();
        sp->clear_all_call_backs();
        sp->clear_trace();
        sp->disable_processing();
        ASSERT_EQ(success_txn.load(), 1);
    }
    {
        // ===========================================================================
        // threads concurrent execution with sequence in begin_txn with different label:
        //
        //      thread1              thread2
        //         |                    |
        //         |                commit_txn1
        //         |                    |
        //         |                    |
        //         |                    |
        //       commit_txn2            |
        //         |                    |
        //         v                    v

        std::mutex go_mutex;
        std::condition_variable go_cv;
        bool go = false;
        auto sp = SyncPoint::get_instance();
        DORIS_CLOUD_DEFER {
            SyncPoint::get_instance()->clear_all_call_backs();
        };

        std::atomic<int32_t> count_txn1 = {0};
        std::atomic<int32_t> count_txn2 = {0};
        std::mutex flow_mutex_1;
        std::condition_variable flow_cv_1;

        int64_t db_id = 19541231112;
        int64_t table_id = 312312321211;
        std::string test_label1 = "test_race_with_diff_label1";
        std::string test_label2 = "test_race_with_diff_label2";

        std::atomic<int32_t> success_txn = {0};

        sp->set_call_back("begin_txn:before:commit_txn:1", [&](auto&& args) {
            std::string label = *try_any_cast<std::string*>(args[0]);
            if (count_txn1.load() == 1) {
                std::unique_lock<std::mutex> flow_lock_1(flow_mutex_1);
                flow_cv_1.wait(flow_lock_1);
            }
            count_txn1++;
            LOG(INFO) << "count_txn1:" << count_txn1 << " label=" << label;
        });

        sp->set_call_back("begin_txn:after:commit_txn:2", [&](auto&& args) {
            int64_t txn_id = *try_any_cast<int64_t*>(args[0]);
            while (count_txn2.load() == 0 && count_txn1.load() == 1) {
                sleep(1);
                flow_cv_1.notify_all();
            }
            count_txn2++;
            LOG(INFO) << "count_txn2:" << count_txn2 << " txn_id=" << txn_id;
        });
        sp->enable_processing();

        std::thread thread1([&] {
            {
                std::unique_lock<std::mutex> _lock(go_mutex);
                go_cv.wait(_lock, [&] { return go; });
            }
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            TxnInfoPB txn_info;
            txn_info.set_db_id(db_id);
            txn_info.set_label(test_label1);
            txn_info.add_table_ids(table_id);
            UniqueIdPB unique_id_pb;
            unique_id_pb.set_hi(1001);
            unique_id_pb.set_lo(11);
            txn_info.mutable_request_id()->CopyFrom(unique_id_pb);
            txn_info.set_timeout_ms(36000);
            req.mutable_txn_info()->CopyFrom(txn_info);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            if (res.status().code() == MetaServiceCode::OK) {
                success_txn++;
            } else {
                ASSERT_EQ(res.status().code(), MetaServiceCode::TXN_LABEL_ALREADY_USED);
            }
        });

        std::thread thread2([&] {
            {
                std::unique_lock<std::mutex> _lock(go_mutex);
                go_cv.wait(_lock, [&] { return go; });
            }
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            TxnInfoPB txn_info;
            txn_info.set_db_id(db_id);
            txn_info.set_label(test_label2);
            txn_info.add_table_ids(table_id);
            txn_info.set_timeout_ms(36000);
            UniqueIdPB unique_id_pb;
            unique_id_pb.set_hi(100);
            unique_id_pb.set_lo(10);
            txn_info.mutable_request_id()->CopyFrom(unique_id_pb);
            req.mutable_txn_info()->CopyFrom(txn_info);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            if (res.status().code() == MetaServiceCode::OK) {
                success_txn++;
            } else {
                ASSERT_EQ(res.status().code(), MetaServiceCode::TXN_LABEL_ALREADY_USED);
            }
        });

        std::unique_lock<std::mutex> go_lock(go_mutex);
        go = true;
        go_lock.unlock();
        go_cv.notify_all();

        thread1.join();
        thread2.join();
        sp->clear_all_call_backs();
        sp->clear_trace();
        sp->disable_processing();
        ASSERT_EQ(success_txn.load(), 2);
    }
    {
        // test reuse label
        // 1. beigin_txn
        // 2. abort_txn
        // 3. begin_txn again can successfully

        std::string cloud_unique_id = "test_cloud_unique_id";
        int64_t db_id = 124343989;
        int64_t table_id = 1231311;
        int64_t txn_id = -1;
        std::string label = "test_reuse_label";
        {
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id(cloud_unique_id);
            TxnInfoPB txn_info;
            txn_info.set_db_id(db_id);
            txn_info.set_label(label);
            txn_info.add_table_ids(table_id);
            txn_info.set_timeout_ms(36000);
            UniqueIdPB unique_id_pb;
            unique_id_pb.set_hi(100);
            unique_id_pb.set_lo(10);
            txn_info.mutable_request_id()->CopyFrom(unique_id_pb);
            req.mutable_txn_info()->CopyFrom(txn_info);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            txn_id = res.txn_id();
        }
        // abort txn
        {
            brpc::Controller cntl;
            AbortTxnRequest req;
            req.set_cloud_unique_id(cloud_unique_id);
            ASSERT_GT(txn_id, 0);
            req.set_txn_id(txn_id);
            req.set_reason("test");
            AbortTxnResponse res;
            meta_service->abort_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(res.txn_info().status(), TxnStatusPB::TXN_STATUS_ABORTED);
        }
        {
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id(cloud_unique_id);
            TxnInfoPB txn_info;
            txn_info.set_db_id(db_id);
            txn_info.set_label(label);
            txn_info.add_table_ids(table_id);
            UniqueIdPB unique_id_pb;
            unique_id_pb.set_hi(100);
            unique_id_pb.set_lo(10);
            txn_info.mutable_request_id()->CopyFrom(unique_id_pb);
            txn_info.set_timeout_ms(36000);
            req.mutable_txn_info()->CopyFrom(txn_info);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_GT(res.txn_id(), txn_id);
        }
    }

    {
        // test reuse label exceed max_num_aborted_txn

        std::string cloud_unique_id = "test_cloud_unique_id";
        int64_t db_id = 124343989;
        int64_t table_id = 12897811;
        int64_t txn_id = -1;
        std::string label = "test_max_num_aborted_txn_label";
        for (int i = 0; i < config::max_num_aborted_txn; i++) {
            {
                brpc::Controller cntl;
                BeginTxnRequest req;
                req.set_cloud_unique_id(cloud_unique_id);
                TxnInfoPB txn_info;
                txn_info.set_db_id(db_id);
                txn_info.set_label(label);
                txn_info.add_table_ids(table_id);
                txn_info.set_timeout_ms(36000);
                UniqueIdPB unique_id_pb;
                unique_id_pb.set_hi(100);
                unique_id_pb.set_lo(10);
                txn_info.mutable_request_id()->CopyFrom(unique_id_pb);
                req.mutable_txn_info()->CopyFrom(txn_info);
                BeginTxnResponse res;
                meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                        &req, &res, nullptr);
                ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
                txn_id = res.txn_id();
            }
            // abort txn
            {
                brpc::Controller cntl;
                AbortTxnRequest req;
                req.set_cloud_unique_id(cloud_unique_id);
                ASSERT_GT(txn_id, 0);
                req.set_txn_id(txn_id);
                req.set_reason("test");
                AbortTxnResponse res;
                meta_service->abort_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                        &req, &res, nullptr);
                ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
                ASSERT_EQ(res.txn_info().status(), TxnStatusPB::TXN_STATUS_ABORTED);
            }
        }
        {
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id(cloud_unique_id);
            TxnInfoPB txn_info;
            txn_info.set_db_id(db_id);
            txn_info.set_label(label);
            txn_info.add_table_ids(table_id);
            UniqueIdPB unique_id_pb;
            unique_id_pb.set_hi(100);
            unique_id_pb.set_lo(10);
            txn_info.mutable_request_id()->CopyFrom(unique_id_pb);
            txn_info.set_timeout_ms(36000);
            req.mutable_txn_info()->CopyFrom(txn_info);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
            ASSERT_TRUE(res.status().msg().find("too many aborted txn for label") !=
                        std::string::npos);
        }
    }
}

TEST(MetaServiceTest, PrecommitTest1) {
    // PrecommitTestCase1: only use db_id for precommit_txn
    auto meta_service = get_meta_service();
    const int64_t db_id = 563413;
    const int64_t table_id = 417417878;
    const std::string& label = "label_123dae121das";
    int64_t txn_id = -1;
    {
        brpc::Controller cntl;
        BeginTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        TxnInfoPB txn_info;
        txn_info.set_db_id(db_id);
        txn_info.set_label(label);
        txn_info.add_table_ids(table_id);
        txn_info.set_timeout_ms(36000);
        req.mutable_txn_info()->CopyFrom(txn_info);
        BeginTxnResponse res;
        meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        txn_id = res.txn_id();
        ASSERT_GT(txn_id, -1);
    }

    {
        brpc::Controller cntl;
        PrecommitTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_precommit_timeout_ms(36000);
        PrecommitTxnResponse res;
        meta_service->precommit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    }

    {
        std::unique_ptr<Transaction> txn;
        TxnErrorCode err = meta_service->txn_kv()->create_txn(&txn);
        ASSERT_EQ(err, TxnErrorCode::TXN_OK);

        const std::string info_key = txn_info_key({mock_instance, db_id, txn_id});
        std::string info_val;
        ASSERT_EQ(txn->get(info_key, &info_val), TxnErrorCode::TXN_OK);
        TxnInfoPB txn_info;
        txn_info.ParseFromString(info_val);
        ASSERT_EQ(txn_info.status(), TxnStatusPB::TXN_STATUS_PREPARED);

        brpc::Controller cntl;
        PrecommitTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_txn_id(txn_id);
        req.set_precommit_timeout_ms(36000);
        PrecommitTxnResponse res;
        meta_service->precommit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

        err = meta_service->txn_kv()->create_txn(&txn);
        ASSERT_EQ(err, TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(info_key, &info_val), TxnErrorCode::TXN_OK);
        txn_info.ParseFromString(info_val);
        ASSERT_EQ(txn_info.status(), TxnStatusPB::TXN_STATUS_PRECOMMITTED);
    }
}

TEST(MetaServiceTest, PrecommitTxnTest2) {
    auto meta_service = get_meta_service();
    const int64_t db_id = 563413;
    const int64_t table_id = 417417878;
    const std::string& label = "label_123dae121das";
    int64_t txn_id = -1;
    // begin txn first
    {
        brpc::Controller cntl;
        BeginTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        TxnInfoPB txn_info;
        txn_info.set_db_id(db_id);
        txn_info.set_label(label);
        txn_info.add_table_ids(table_id);
        txn_info.set_timeout_ms(36000);
        req.mutable_txn_info()->CopyFrom(txn_info);
        BeginTxnResponse res;
        meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        txn_id = res.txn_id();
        ASSERT_GT(txn_id, -1);
    }

    // case: txn's status should be TXN_STATUS_PRECOMMITTED
    {
        std::unique_ptr<Transaction> txn;
        TxnErrorCode err = meta_service->txn_kv()->create_txn(&txn);
        ASSERT_EQ(err, TxnErrorCode::TXN_OK);

        const std::string info_key = txn_info_key({mock_instance, db_id, txn_id});
        std::string info_val;
        ASSERT_EQ(txn->get(info_key, &info_val), TxnErrorCode::TXN_OK);
        TxnInfoPB txn_info;
        txn_info.ParseFromString(info_val);
        // before call precommit_txn, txn's status is TXN_STATUS_PREPARED
        ASSERT_EQ(txn_info.status(), TxnStatusPB::TXN_STATUS_PREPARED);

        brpc::Controller cntl;
        PrecommitTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_db_id(db_id);
        req.set_txn_id(txn_id);
        req.set_precommit_timeout_ms(36000);
        PrecommitTxnResponse res;
        meta_service->precommit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

        err = meta_service->txn_kv()->create_txn(&txn);
        ASSERT_EQ(err, TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(info_key, &info_val), TxnErrorCode::TXN_OK);
        txn_info.ParseFromString(info_val);
        // after call precommit_txn, txn's status is TXN_STATUS_PRECOMMITTED
        ASSERT_EQ(txn_info.status(), TxnStatusPB::TXN_STATUS_PRECOMMITTED);
    }

    // case: when txn's status is TXN_STATUS_ABORTED/TXN_STATUS_VISIBLE/TXN_STATUS_PRECOMMITTED
    {
        // TXN_STATUS_ABORTED
        std::unique_ptr<Transaction> txn;
        TxnErrorCode err = meta_service->txn_kv()->create_txn(&txn);
        ASSERT_EQ(err, TxnErrorCode::TXN_OK);

        const std::string info_key = txn_info_key({mock_instance, db_id, txn_id});
        std::string info_val;
        ASSERT_EQ(txn->get(info_key, &info_val), TxnErrorCode::TXN_OK);
        TxnInfoPB txn_info;
        txn_info.ParseFromString(info_val);
        txn_info.set_status(TxnStatusPB::TXN_STATUS_ABORTED);
        info_val.clear();
        txn_info.SerializeToString(&info_val);
        txn->put(info_key, info_val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

        brpc::Controller cntl;
        PrecommitTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_db_id(db_id);
        req.set_txn_id(txn_id);
        req.set_precommit_timeout_ms(36000);
        PrecommitTxnResponse res;
        meta_service->precommit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::TXN_ALREADY_ABORTED);

        // TXN_STATUS_VISIBLE
        txn_info.set_status(TxnStatusPB::TXN_STATUS_VISIBLE);
        info_val.clear();
        txn_info.SerializeToString(&info_val);
        txn->put(info_key, info_val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
        meta_service->precommit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::TXN_ALREADY_VISIBLE);

        // TXN_STATUS_PRECOMMITTED
        txn_info.set_status(TxnStatusPB::TXN_STATUS_PRECOMMITTED);
        info_val.clear();
        txn_info.SerializeToString(&info_val);
        txn->put(info_key, info_val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
        meta_service->precommit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::TXN_ALREADY_PRECOMMITED);
    }
}

TEST(MetaServiceTest, CommitTxnTest) {
    auto meta_service = get_meta_service();
    int64_t table_id = 1234;
    int64_t index_id = 1235;
    int64_t partition_id = 1236;

    // case: first version of rowset
    {
        int64_t txn_id = -1;
        // begin txn
        {
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            TxnInfoPB txn_info_pb;
            txn_info_pb.set_db_id(666);
            txn_info_pb.set_label("test_label");
            txn_info_pb.add_table_ids(1234);
            txn_info_pb.set_timeout_ms(36000);
            req.mutable_txn_info()->CopyFrom(txn_info_pb);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            txn_id = res.txn_id();
        }

        // mock rowset and tablet
        int64_t tablet_id_base = 1103;
        for (int i = 0; i < 5; ++i) {
            create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id_base + i);
            auto tmp_rowset = create_rowset(txn_id, tablet_id_base + i, partition_id);
            CreateRowsetResponse res;
            commit_rowset(meta_service.get(), tmp_rowset, res);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }

        // precommit txn
        {
            brpc::Controller cntl;
            PrecommitTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_db_id(666);
            req.set_txn_id(txn_id);
            req.set_precommit_timeout_ms(36000);
            PrecommitTxnResponse res;
            meta_service->precommit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                        &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }

        // commit txn
        {
            brpc::Controller cntl;
            CommitTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_db_id(666);
            req.set_txn_id(txn_id);
            CommitTxnResponse res;
            meta_service->commit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }

        // doubly commit txn
        {
            brpc::Controller cntl;
            CommitTxnRequest req;
            auto db_id = 666;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_db_id(db_id);
            req.set_txn_id(txn_id);
            CommitTxnResponse res;
            meta_service->commit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            auto found = res.status().msg().find(fmt::format(
                    "transaction is already visible: db_id={} txn_id={}", db_id, txn_id));
            ASSERT_TRUE(found != std::string::npos);
        }

        // doubly commit txn(2pc)
        {
            brpc::Controller cntl;
            CommitTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_db_id(666);
            req.set_txn_id(txn_id);
            req.set_is_2pc(true);
            CommitTxnResponse res;
            meta_service->commit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::TXN_ALREADY_VISIBLE);
            auto found = res.status().msg().find(
                    fmt::format("transaction [{}] is already visible, not pre-committed.", txn_id));
            ASSERT_TRUE(found != std::string::npos);
        }
    }
}

TEST(MetaServiceTest, CommitTxnExpiredTest) {
    auto meta_service = get_meta_service();

    int64_t table_id = 1234789234;
    int64_t index_id = 1235;
    int64_t partition_id = 1236;

    // case: first version of rowset
    {
        int64_t txn_id = -1;
        int64_t db_id = 713232132;
        // begin txn
        {
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            TxnInfoPB txn_info_pb;
            txn_info_pb.set_db_id(db_id);
            txn_info_pb.set_label("test_commit_txn_expired");
            txn_info_pb.add_table_ids(1234789234);
            txn_info_pb.set_timeout_ms(1);
            req.mutable_txn_info()->CopyFrom(txn_info_pb);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            txn_id = res.txn_id();
        }

        // mock rowset and tablet
        int64_t tablet_id_base = 1103;
        for (int i = 0; i < 5; ++i) {
            create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id_base + i);
            auto tmp_rowset = create_rowset(txn_id, tablet_id_base + i, partition_id);
            CreateRowsetResponse res;
            commit_rowset(meta_service.get(), tmp_rowset, res);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }
        // sleep 1 second for txn timeout
        sleep(1);
        // commit txn
        {
            brpc::Controller cntl;
            CommitTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_db_id(db_id);
            req.set_txn_id(txn_id);
            CommitTxnResponse res;
            meta_service->commit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::UNDEFINED_ERR);
            ASSERT_TRUE(res.status().msg().find("txn is expired, not allow to commit txn_id=") !=
                        std::string::npos);
        }
    }
}

void create_and_commit_rowset(MetaServiceProxy* meta_service, int64_t table_id, int64_t index_id,
                              int64_t partition_id, int64_t tablet_id, int64_t txn_id) {
    create_tablet(meta_service, table_id, index_id, partition_id, tablet_id);
    auto tmp_rowset = create_rowset(txn_id, tablet_id, partition_id);
    CreateRowsetResponse res;
    commit_rowset(meta_service, tmp_rowset, res);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
}

TEST(MetaServiceTest, CommitTxnWithSubTxnTest) {
    auto meta_service = get_meta_service();
    int64_t db_id = 98131;
    int64_t txn_id = -1;
    int64_t t1 = 10;
    int64_t t1_index = 100;
    int64_t t1_p1 = 11;
    int64_t t1_p1_t1 = 12;
    int64_t t1_p1_t2 = 13;
    int64_t t1_p2 = 14;
    int64_t t1_p2_t1 = 15;
    int64_t t2 = 16;
    int64_t t2_index = 101;
    int64_t t2_p3 = 17;
    int64_t t2_p3_t1 = 18;
    [[maybe_unused]] int64_t t2_p4 = 19;
    [[maybe_unused]] int64_t t2_p4_t1 = 20;
    std::string label = "test_label";
    std::string label2 = "test_label_0";
    // begin txn
    {
        brpc::Controller cntl;
        BeginTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        TxnInfoPB txn_info_pb;
        txn_info_pb.set_db_id(db_id);
        txn_info_pb.set_label(label);
        txn_info_pb.add_table_ids(t1);
        txn_info_pb.set_timeout_ms(36000);
        req.mutable_txn_info()->CopyFrom(txn_info_pb);
        BeginTxnResponse res;
        meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        txn_id = res.txn_id();
    }

    // mock rowset and tablet: for sub_txn1
    int64_t sub_txn_id1 = txn_id;
    create_and_commit_rowset(meta_service.get(), t1, t1_index, t1_p1, t1_p1_t1, sub_txn_id1);
    create_and_commit_rowset(meta_service.get(), t1, t1_index, t1_p1, t1_p1_t2, sub_txn_id1);
    create_and_commit_rowset(meta_service.get(), t1, t1_index, t1_p2, t1_p2_t1, sub_txn_id1);

    // begin_sub_txn2
    int64_t sub_txn_id2 = -1;
    {
        brpc::Controller cntl;
        BeginSubTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_txn_id(txn_id);
        req.set_sub_txn_num(0);
        req.set_db_id(db_id);
        req.set_label(label2);
        req.mutable_table_ids()->Add(t1);
        req.mutable_table_ids()->Add(t2);
        BeginSubTxnResponse res;
        meta_service->begin_sub_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.txn_info().table_ids().size(), 2);
        ASSERT_EQ(res.txn_info().sub_txn_ids().size(), 1);
        ASSERT_TRUE(res.has_sub_txn_id());
        sub_txn_id2 = res.sub_txn_id();
        ASSERT_EQ(sub_txn_id2, res.txn_info().sub_txn_ids()[0]);
    }
    // mock rowset and tablet: for sub_txn3
    create_and_commit_rowset(meta_service.get(), t2, t2_index, t2_p3, t2_p3_t1, sub_txn_id2);

    // begin_sub_txn3
    int64_t sub_txn_id3 = -1;
    {
        brpc::Controller cntl;
        BeginSubTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_txn_id(txn_id);
        req.set_sub_txn_num(1);
        req.set_db_id(db_id);
        req.set_label("test_label_1");
        req.mutable_table_ids()->Add(t1);
        req.mutable_table_ids()->Add(t2);
        req.mutable_table_ids()->Add(t1);
        BeginSubTxnResponse res;
        meta_service->begin_sub_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.txn_info().table_ids().size(), 3);
        ASSERT_EQ(res.txn_info().sub_txn_ids().size(), 2);
        ASSERT_TRUE(res.has_sub_txn_id());
        sub_txn_id3 = res.sub_txn_id();
        ASSERT_EQ(sub_txn_id3, res.txn_info().sub_txn_ids()[1]);
    }
    // mock rowset and tablet: for sub_txn3
    create_and_commit_rowset(meta_service.get(), t1, t1_index, t1_p1, t1_p1_t1, sub_txn_id3);
    create_and_commit_rowset(meta_service.get(), t1, t1_index, t1_p1, t1_p1_t2, sub_txn_id3);

    // commit txn
    CommitTxnRequest req;
    {
        brpc::Controller cntl;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_db_id(666);
        req.set_txn_id(txn_id);
        req.set_is_txn_load(true);

        SubTxnInfo sub_txn_info1;
        sub_txn_info1.set_sub_txn_id(sub_txn_id1);
        sub_txn_info1.set_table_id(t1);
        sub_txn_info1.mutable_base_tablet_ids()->Add(t1_p1_t1);
        sub_txn_info1.mutable_base_tablet_ids()->Add(t1_p1_t2);
        sub_txn_info1.mutable_base_tablet_ids()->Add(t1_p2_t1);

        SubTxnInfo sub_txn_info2;
        sub_txn_info2.set_sub_txn_id(sub_txn_id2);
        sub_txn_info2.set_table_id(t2);
        sub_txn_info2.mutable_base_tablet_ids()->Add(t2_p3_t1);

        SubTxnInfo sub_txn_info3;
        sub_txn_info3.set_sub_txn_id(sub_txn_id3);
        sub_txn_info3.set_table_id(t1);
        sub_txn_info3.mutable_base_tablet_ids()->Add(t1_p1_t1);
        sub_txn_info3.mutable_base_tablet_ids()->Add(t1_p1_t2);

        req.mutable_sub_txn_infos()->Add(std::move(sub_txn_info1));
        req.mutable_sub_txn_infos()->Add(std::move(sub_txn_info2));
        req.mutable_sub_txn_infos()->Add(std::move(sub_txn_info3));
        CommitTxnResponse res;
        meta_service->commit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                 &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        // std::cout << res.DebugString() << std::endl;
        ASSERT_EQ(res.table_ids().size(), 3);

        ASSERT_EQ(res.table_ids()[0], t2);
        ASSERT_EQ(res.partition_ids()[0], t2_p3);
        ASSERT_EQ(res.versions()[0], 2);

        ASSERT_EQ(res.table_ids()[1], t1);
        ASSERT_EQ(res.partition_ids()[1], t1_p2);
        ASSERT_EQ(res.versions()[1], 2);

        ASSERT_EQ(res.table_ids()[2], t1);
        ASSERT_EQ(res.partition_ids()[2], t1_p1) << res.ShortDebugString();
        ASSERT_EQ(res.versions()[2], 3) << res.ShortDebugString();
    }

    // doubly commit txn
    {
        brpc::Controller cntl;
        CommitTxnResponse res;
        meta_service->commit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                 &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        auto found = res.status().msg().find(
                fmt::format("transaction is already visible: db_id={} txn_id={}", db_id, txn_id));
        ASSERT_TRUE(found != std::string::npos);
    }

    // check kv
    {
        std::unique_ptr<Transaction> txn;
        TxnErrorCode err = meta_service->txn_kv()->create_txn(&txn);
        ASSERT_EQ(err, TxnErrorCode::TXN_OK);

        // txn_info
        std::string info_key = txn_info_key({mock_instance, db_id, txn_id});
        std::string info_val;
        ASSERT_EQ(txn->get(info_key, &info_val), TxnErrorCode::TXN_OK);
        TxnInfoPB txn_info;
        txn_info.ParseFromString(info_val);
        ASSERT_EQ(txn_info.status(), TxnStatusPB::TXN_STATUS_VISIBLE);

        info_key = txn_info_key({mock_instance, db_id, sub_txn_id2});
        ASSERT_EQ(txn->get(info_key, &info_val), TxnErrorCode::TXN_KEY_NOT_FOUND);

        // txn_index
        std::string index_key = txn_index_key({mock_instance, txn_id});
        std::string index_val;
        ASSERT_EQ(txn->get(index_key, &index_val), TxnErrorCode::TXN_OK);
        TxnIndexPB txn_index;
        txn_index.ParseFromString(index_val);
        ASSERT_TRUE(txn_index.has_tablet_index());

        index_key = txn_index_key({mock_instance, sub_txn_id3});
        ASSERT_EQ(txn->get(index_key, &index_val), TxnErrorCode::TXN_OK);
        txn_index.ParseFromString(index_val);
        ASSERT_TRUE(txn_index.has_tablet_index());

        // txn_label
        std::string label_key = txn_label_key({mock_instance, db_id, label});
        std::string label_val;
        ASSERT_EQ(txn->get(label_key, &label_val), TxnErrorCode::TXN_OK);

        label_key = txn_label_key({mock_instance, db_id, label2});
        ASSERT_EQ(txn->get(label_key, &label_val), TxnErrorCode::TXN_KEY_NOT_FOUND);

        // txn_running
        std::string running_key = txn_running_key({mock_instance, db_id, txn_id});
        std::string running_val;
        ASSERT_EQ(txn->get(running_key, &running_val), TxnErrorCode::TXN_KEY_NOT_FOUND);

        running_key = txn_running_key({mock_instance, db_id, sub_txn_id3});
        ASSERT_EQ(txn->get(running_key, &running_val), TxnErrorCode::TXN_KEY_NOT_FOUND);

        // tmp rowset
        int64_t ids[] = {txn_id, sub_txn_id1, sub_txn_id2, sub_txn_id3};
        for (auto id : ids) {
            MetaRowsetTmpKeyInfo rs_tmp_key_info0 {mock_instance, id, 0};
            MetaRowsetTmpKeyInfo rs_tmp_key_info1 {mock_instance, id + 1, 0};
            std::string rs_tmp_key0;
            std::string rs_tmp_key1;
            meta_rowset_tmp_key(rs_tmp_key_info0, &rs_tmp_key0);
            meta_rowset_tmp_key(rs_tmp_key_info1, &rs_tmp_key1);
            std::unique_ptr<RangeGetIterator> it;
            ASSERT_EQ(txn->get(rs_tmp_key0, rs_tmp_key1, &it, true), TxnErrorCode::TXN_OK);
            ASSERT_FALSE(it->has_next());
        }

        // partition version
        std::string ver_key = partition_version_key({mock_instance, db_id, t2, t2_p3});
        std::string ver_val;
        ASSERT_EQ(txn->get(ver_key, &ver_val), TxnErrorCode::TXN_OK);
        VersionPB version;
        version.ParseFromString(ver_val);
        ASSERT_EQ(version.version(), 2);

        ver_key = partition_version_key({mock_instance, db_id, t1, t1_p2});
        ASSERT_EQ(txn->get(ver_key, &ver_val), TxnErrorCode::TXN_OK);
        version.ParseFromString(ver_val);
        ASSERT_EQ(version.version(), 2);

        ver_key = partition_version_key({mock_instance, db_id, t1, t1_p1});
        ASSERT_EQ(txn->get(ver_key, &ver_val), TxnErrorCode::TXN_OK);
        version.ParseFromString(ver_val);
        ASSERT_EQ(version.version(), 3);

        // table version
        std::string table_ver_key = table_version_key({mock_instance, db_id, t1});
        std::string table_ver_val;
        ASSERT_EQ(txn->get(table_ver_key, &table_ver_val), TxnErrorCode::TXN_OK);
        auto val_int = *reinterpret_cast<const int64_t*>(table_ver_val.data());
        ASSERT_EQ(val_int, 1);

        table_version_key({mock_instance, db_id, t2});
        ASSERT_EQ(txn->get(table_ver_key, &table_ver_val), TxnErrorCode::TXN_OK);
        val_int = *reinterpret_cast<const int64_t*>(table_ver_val.data());
        ASSERT_EQ(val_int, 1);
    }
}

TEST(MetaServiceTest, CommitTxnWithSubTxnTest2) {
    auto meta_service = get_meta_service();
    int64_t db_id = 99131;
    int64_t txn_id = -1;
    int64_t t1 = 20;
    int64_t t1_index = 200;
    int64_t t1_p1 = 21;
    int64_t t1_p1_t1 = 22;
    int64_t t1_p1_t2 = 23;
    int64_t t1_p2 = 24;
    int64_t t1_p2_t1 = 25;
    int64_t t2 = 26;
    int64_t t2_index = 201;
    int64_t t2_p3 = 27;
    int64_t t2_p3_t1 = 28;
    [[maybe_unused]] int64_t t2_p4 = 29;
    [[maybe_unused]] int64_t t2_p4_t1 = 30;
    std::string label = "test_label_10";
    std::string label2 = "test_label_11";
    // begin txn
    {
        brpc::Controller cntl;
        BeginTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        TxnInfoPB txn_info_pb;
        txn_info_pb.set_db_id(db_id);
        txn_info_pb.set_label(label);
        txn_info_pb.add_table_ids(t1);
        txn_info_pb.set_timeout_ms(36000);
        req.mutable_txn_info()->CopyFrom(txn_info_pb);
        BeginTxnResponse res;
        meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        txn_id = res.txn_id();
    }

    std::vector<SubTxnInfo> sub_txn_infos;
    for (int i = 0; i < 500; i++) {
        int64_t sub_txn_id1 = -1;
        if (i == 0) {
            sub_txn_id1 = txn_id;
        } else {
            brpc::Controller cntl;
            BeginSubTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_txn_id(txn_id);
            req.set_sub_txn_num(sub_txn_infos.size() - 1);
            req.set_db_id(db_id);
            req.set_label(label2);
            req.mutable_table_ids()->Add(t1);
            if (i > 0) {
                req.mutable_table_ids()->Add(t2);
            }
            BeginSubTxnResponse res;
            meta_service->begin_sub_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                        &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(res.txn_info().table_ids().size(), i == 0 ? 1 : 2);
            ASSERT_EQ(res.txn_info().sub_txn_ids().size(), sub_txn_infos.size());
            ASSERT_TRUE(res.has_sub_txn_id());
            sub_txn_id1 = res.sub_txn_id();
            ASSERT_EQ(sub_txn_id1,
                      res.txn_info().sub_txn_ids()[res.txn_info().sub_txn_ids().size() - 1]);
        }
        // mock rowset and tablet: for
        create_and_commit_rowset(meta_service.get(), t1, t1_index, t1_p1, t1_p1_t1, sub_txn_id1);
        create_and_commit_rowset(meta_service.get(), t1, t1_index, t1_p1, t1_p1_t2, sub_txn_id1);
        create_and_commit_rowset(meta_service.get(), t1, t1_index, t1_p2, t1_p2_t1, sub_txn_id1);
        // generate sub_txn_info
        {
            SubTxnInfo sub_txn_info1;
            sub_txn_info1.set_sub_txn_id(sub_txn_id1);
            sub_txn_info1.set_table_id(t1);
            sub_txn_info1.mutable_base_tablet_ids()->Add(t1_p1_t1);
            sub_txn_info1.mutable_base_tablet_ids()->Add(t1_p1_t2);
            sub_txn_info1.mutable_base_tablet_ids()->Add(t1_p2_t1);
            sub_txn_infos.push_back(sub_txn_info1);
        }

        // begin_sub_txn2
        int64_t sub_txn_id2 = -1;
        {
            brpc::Controller cntl;
            BeginSubTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_txn_id(txn_id);
            req.set_sub_txn_num(sub_txn_infos.size() - 1);
            req.set_db_id(db_id);
            req.set_label(label2);
            req.mutable_table_ids()->Add(t1);
            req.mutable_table_ids()->Add(t2);
            BeginSubTxnResponse res;
            meta_service->begin_sub_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                        &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(res.txn_info().table_ids().size(), 2);
            ASSERT_EQ(res.txn_info().sub_txn_ids().size(), sub_txn_infos.size());
            ASSERT_TRUE(res.has_sub_txn_id());
            sub_txn_id2 = res.sub_txn_id();
        }
        // mock rowset and tablet: for sub_txn3
        create_and_commit_rowset(meta_service.get(), t2, t2_index, t2_p3, t2_p3_t1, sub_txn_id2);
        {
            SubTxnInfo sub_txn_info2;
            sub_txn_info2.set_sub_txn_id(sub_txn_id2);
            sub_txn_info2.set_table_id(t2);
            sub_txn_info2.mutable_base_tablet_ids()->Add(t2_p3_t1);
            sub_txn_infos.push_back(sub_txn_info2);
        }

        // begin_sub_txn3
        int64_t sub_txn_id3 = -1;
        {
            brpc::Controller cntl;
            BeginSubTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_txn_id(txn_id);
            req.set_sub_txn_num(sub_txn_infos.size() - 1);
            req.set_db_id(db_id);
            req.set_label(label2);
            req.mutable_table_ids()->Add(t1);
            req.mutable_table_ids()->Add(t2);
            BeginSubTxnResponse res;
            meta_service->begin_sub_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                        &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(res.txn_info().table_ids().size(), 2);
            ASSERT_EQ(res.txn_info().sub_txn_ids().size(), sub_txn_infos.size());
            ASSERT_TRUE(res.has_sub_txn_id());
            sub_txn_id3 = res.sub_txn_id();
        }
        // mock rowset and tablet: for sub_txn3
        create_and_commit_rowset(meta_service.get(), t1, t1_index, t1_p1, t1_p1_t1, sub_txn_id3);
        create_and_commit_rowset(meta_service.get(), t1, t1_index, t1_p1, t1_p1_t2, sub_txn_id3);
        {
            SubTxnInfo sub_txn_info3;
            sub_txn_info3.set_sub_txn_id(sub_txn_id3);
            sub_txn_info3.set_table_id(t1);
            sub_txn_info3.mutable_base_tablet_ids()->Add(t1_p1_t1);
            sub_txn_info3.mutable_base_tablet_ids()->Add(t1_p1_t2);
            sub_txn_infos.push_back(sub_txn_info3);
        }
    }

    // commit txn
    CommitTxnRequest req;
    {
        brpc::Controller cntl;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_db_id(666);
        req.set_txn_id(txn_id);
        req.set_is_txn_load(true);

        for (const auto& sub_txn_info : sub_txn_infos) {
            req.add_sub_txn_infos()->CopyFrom(sub_txn_info);
        }
        CommitTxnResponse res;
        meta_service->commit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                 &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        std::cout << res.DebugString() << std::endl;
        ASSERT_EQ(res.table_ids().size(), 3);

        ASSERT_EQ(res.table_ids()[0], t2);
        ASSERT_EQ(res.partition_ids()[0], t2_p3);
        ASSERT_EQ(res.versions()[0], 501);

        ASSERT_EQ(res.table_ids()[1], t1);
        ASSERT_EQ(res.partition_ids()[1], t1_p2);
        ASSERT_EQ(res.versions()[1], 501);

        ASSERT_EQ(res.table_ids()[2], t1);
        ASSERT_EQ(res.partition_ids()[2], t1_p1);
        ASSERT_EQ(res.versions()[2], 1001);
    }
}

TEST(MetaServiceTest, BeginAndAbortSubTxnTest) {
    auto meta_service = get_meta_service();
    long db_id = 98762;
    int64_t txn_id = -1;
    // begin txn
    {
        brpc::Controller cntl;
        BeginTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        TxnInfoPB txn_info_pb;
        txn_info_pb.set_db_id(db_id);
        txn_info_pb.set_label("test_label");
        txn_info_pb.add_table_ids(1234);
        txn_info_pb.set_timeout_ms(36000);
        req.mutable_txn_info()->CopyFrom(txn_info_pb);
        BeginTxnResponse res;
        meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        txn_id = res.txn_id();
    }
    // case: begin 2 sub txn
    int64_t sub_txn_id1 = -1;
    int64_t sub_txn_id2 = -1;
    for (int i = 0; i < 2; i++) {
        {
            brpc::Controller cntl;
            BeginSubTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_txn_id(txn_id);
            req.set_sub_txn_num(i);
            req.set_db_id(db_id);
            req.set_label("test_label_" + std::to_string(i));
            req.mutable_table_ids()->Add(1234);
            req.mutable_table_ids()->Add(1235);
            if (i == 1) {
                req.mutable_table_ids()->Add(1235);
            }
            BeginSubTxnResponse res;
            meta_service->begin_sub_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                        &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(res.txn_info().table_ids().size(), i == 0 ? 2 : 3);
            ASSERT_EQ(res.txn_info().sub_txn_ids().size(), i == 0 ? 1 : 2);
            ASSERT_TRUE(res.has_sub_txn_id());
            if (i == 0) {
                sub_txn_id1 = res.sub_txn_id();
                ASSERT_EQ(sub_txn_id1, res.txn_info().sub_txn_ids()[0]);
            } else {
                sub_txn_id2 = res.sub_txn_id();
                ASSERT_EQ(sub_txn_id1, res.txn_info().sub_txn_ids()[0]);
                ASSERT_EQ(sub_txn_id2, res.txn_info().sub_txn_ids()[1]);
            }
        }
        // get txn state
        {
            brpc::Controller cntl;
            GetTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_txn_id(txn_id);
            GetTxnResponse res;
            meta_service->get_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                  &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(res.txn_info().table_ids().size(), i == 0 ? 2 : 3);
            ASSERT_EQ(res.txn_info().table_ids()[0], 1234);
            ASSERT_EQ(res.txn_info().table_ids()[1], 1235);
            if (i == 1) {
                ASSERT_EQ(res.txn_info().table_ids()[2], 1235);
            }
        }
    }
    // case: abort sub txn2 twice
    {
        for (int i = 0; i < 2; i++) {
            brpc::Controller cntl;
            AbortSubTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_txn_id(txn_id);
            req.set_sub_txn_id(sub_txn_id2);
            req.set_sub_txn_num(2);
            req.set_db_id(db_id);
            req.mutable_table_ids()->Add(1234);
            req.mutable_table_ids()->Add(1235);
            AbortSubTxnResponse res;
            meta_service->abort_sub_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                        &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            // check txn state
            ASSERT_EQ(res.txn_info().table_ids().size(), 2);
            ASSERT_EQ(res.txn_info().sub_txn_ids().size(), 2);
            ASSERT_EQ(sub_txn_id1, res.txn_info().sub_txn_ids()[0]);
            ASSERT_EQ(sub_txn_id2, res.txn_info().sub_txn_ids()[1]);
        }
        // get txn state
        {
            brpc::Controller cntl;
            GetTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_txn_id(txn_id);
            GetTxnResponse res;
            meta_service->get_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                  &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(res.txn_info().table_ids().size(), 2);
            ASSERT_EQ(res.txn_info().table_ids()[0], 1234);
            ASSERT_EQ(res.txn_info().table_ids()[1], 1235);
        }
    }
    // check label key does not exist
    for (int i = 0; i < 2; i++) {
        std::string key =
                txn_label_key({"test_instance", db_id, "test_label_" + std::to_string(i)});
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    }
    // check txn index key exist
    for (auto i : {sub_txn_id1, sub_txn_id2}) {
        std::string key = txn_index_key({"test_instance", i});
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
    }
}

TEST(MetaServiceTest, AbortTxnTest) {
    auto meta_service = get_meta_service();

    // case: abort txn by txn_id
    {
        int64_t db_id = 666;
        int64_t table_id = 12345;
        std::string label = "abort_txn_by_txn_id";
        std::string cloud_unique_id = "test_cloud_unique_id";
        int64_t tablet_id_base = 1104;
        int64_t txn_id = -1;
        // begin txn
        {
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id(cloud_unique_id);
            TxnInfoPB txn_info_pb;
            txn_info_pb.set_db_id(db_id);
            txn_info_pb.set_label(label);
            txn_info_pb.add_table_ids(table_id);
            txn_info_pb.set_timeout_ms(36000);
            req.mutable_txn_info()->CopyFrom(txn_info_pb);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            txn_id = res.txn_id();
        }

        // mock rowset and tablet
        for (int i = 0; i < 5; ++i) {
            create_tablet(meta_service.get(), 12345, 1235, 1236, tablet_id_base + i);
            auto tmp_rowset = create_rowset(txn_id, tablet_id_base + i);
            CreateRowsetResponse res;
            commit_rowset(meta_service.get(), tmp_rowset, res);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }

        // abort txn by txn_id
        {
            brpc::Controller cntl;
            AbortTxnRequest req;
            req.set_cloud_unique_id(cloud_unique_id);
            req.set_txn_id(txn_id);
            req.set_reason("test");
            AbortTxnResponse res;
            meta_service->abort_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(res.txn_info().status(), TxnStatusPB::TXN_STATUS_ABORTED);
        }
    }

    // case: abort txn by db_id + label
    {
        int64_t db_id = 66631313131;
        int64_t table_id = 12345;
        std::string label = "abort_txn_by_db_id_and_label";
        std::string cloud_unique_id = "test_cloud_unique_id";
        int64_t tablet_id_base = 1104;
        int64_t txn_id = -1;
        // begin txn
        {
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id(cloud_unique_id);
            TxnInfoPB txn_info_pb;
            txn_info_pb.set_db_id(db_id);
            txn_info_pb.set_label(label);
            txn_info_pb.add_table_ids(table_id);
            txn_info_pb.set_timeout_ms(36000);
            req.mutable_txn_info()->CopyFrom(txn_info_pb);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            txn_id = res.txn_id();
        }

        // mock rowset and tablet
        for (int i = 0; i < 5; ++i) {
            create_tablet(meta_service.get(), table_id, 1235, 1236, tablet_id_base + i);
            auto tmp_rowset = create_rowset(txn_id, tablet_id_base + i);
            CreateRowsetResponse res;
            commit_rowset(meta_service.get(), tmp_rowset, res);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }

        // abort txn by db_id and label
        {
            brpc::Controller cntl;
            AbortTxnRequest req;
            req.set_cloud_unique_id(cloud_unique_id);
            req.set_db_id(db_id);
            req.set_label(label);
            req.set_reason("test");
            AbortTxnResponse res;
            meta_service->abort_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(res.txn_info().status(), TxnStatusPB::TXN_STATUS_ABORTED);

            std::string recycle_txn_key_;
            std::string recycle_txn_val;
            RecycleTxnKeyInfo recycle_txn_key_info {mock_instance, db_id, txn_id};
            recycle_txn_key(recycle_txn_key_info, &recycle_txn_key_);
            std::unique_ptr<Transaction> txn;
            ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
            ASSERT_EQ(txn->get(recycle_txn_key_, &recycle_txn_val), TxnErrorCode::TXN_OK);
            ASSERT_NE(txn_id, -1);
        }
    }
}

TEST(MetaServiceTest, GetCurrentMaxTxnIdTest) {
    auto meta_service = get_meta_service();

    const int64_t db_id = 123;
    const std::string label = "test_label123";
    const std::string cloud_unique_id = "test_cloud_unique_id";

    brpc::Controller begin_txn_cntl;
    BeginTxnRequest begin_txn_req;
    BeginTxnResponse begin_txn_res;
    TxnInfoPB txn_info_pb;

    begin_txn_req.set_cloud_unique_id(cloud_unique_id);
    txn_info_pb.set_db_id(db_id);
    txn_info_pb.set_label(label);
    txn_info_pb.add_table_ids(12345);
    txn_info_pb.set_timeout_ms(36000);
    begin_txn_req.mutable_txn_info()->CopyFrom(txn_info_pb);

    meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&begin_txn_cntl),
                            &begin_txn_req, &begin_txn_res, nullptr);
    ASSERT_EQ(begin_txn_res.status().code(), MetaServiceCode::OK);

    brpc::Controller max_txn_id_cntl;
    GetCurrentMaxTxnRequest max_txn_id_req;
    GetCurrentMaxTxnResponse max_txn_id_res;

    max_txn_id_req.set_cloud_unique_id(cloud_unique_id);

    meta_service->get_current_max_txn_id(
            reinterpret_cast<::google::protobuf::RpcController*>(&max_txn_id_cntl), &max_txn_id_req,
            &max_txn_id_res, nullptr);

    ASSERT_EQ(max_txn_id_res.status().code(), MetaServiceCode::OK);
    ASSERT_GE(max_txn_id_res.current_max_txn_id(), begin_txn_res.txn_id());
}

TEST(MetaServiceTest, AbortTxnWithCoordinatorTest) {
    auto meta_service = get_meta_service();

    const int64_t db_id = 666;
    const int64_t table_id = 777;
    const std::string label = "test_label";
    const std::string cloud_unique_id = "test_cloud_unique_id";
    const int64_t coordinator_id = 15623;
    int64_t cur_time = std::chrono::duration_cast<std::chrono::seconds>(
                               std::chrono::steady_clock::now().time_since_epoch())
                               .count();
    std::string host = "127.0.0.1:15586";
    int64_t txn_id = -1;

    brpc::Controller begin_txn_cntl;
    BeginTxnRequest begin_txn_req;
    BeginTxnResponse begin_txn_res;
    TxnInfoPB txn_info_pb;
    TxnCoordinatorPB coordinator;

    begin_txn_req.set_cloud_unique_id(cloud_unique_id);
    txn_info_pb.set_db_id(db_id);
    txn_info_pb.set_label(label);
    txn_info_pb.add_table_ids(table_id);
    txn_info_pb.set_timeout_ms(36000);
    coordinator.set_id(coordinator_id);
    coordinator.set_ip(host);
    coordinator.set_sourcetype(::doris::cloud::TxnSourceTypePB::TXN_SOURCE_TYPE_BE);
    coordinator.set_start_time(cur_time);
    txn_info_pb.mutable_coordinator()->CopyFrom(coordinator);
    begin_txn_req.mutable_txn_info()->CopyFrom(txn_info_pb);

    meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&begin_txn_cntl),
                            &begin_txn_req, &begin_txn_res, nullptr);
    ASSERT_EQ(begin_txn_res.status().code(), MetaServiceCode::OK);
    txn_id = begin_txn_res.txn_id();
    ASSERT_GT(txn_id, -1);

    brpc::Controller abort_txn_cntl;
    AbortTxnWithCoordinatorRequest abort_txn_req;
    AbortTxnWithCoordinatorResponse abort_txn_resp;

    abort_txn_req.set_id(coordinator_id);
    abort_txn_req.set_ip(host);
    abort_txn_req.set_start_time(cur_time + 3600);

    // first time to check txn conflict
    meta_service->abort_txn_with_coordinator(
            reinterpret_cast<::google::protobuf::RpcController*>(&begin_txn_cntl), &abort_txn_req,
            &abort_txn_resp, nullptr);
    ASSERT_EQ(abort_txn_resp.status().code(), MetaServiceCode::OK);

    brpc::Controller abort_txn_conflict_cntl;
    CheckTxnConflictRequest check_txn_conflict_req;
    CheckTxnConflictResponse check_txn_conflict_res;

    check_txn_conflict_req.set_cloud_unique_id(cloud_unique_id);
    check_txn_conflict_req.set_db_id(db_id);
    check_txn_conflict_req.set_end_txn_id(txn_id + 1);
    check_txn_conflict_req.add_table_ids(table_id);

    // first time to check txn conflict
    meta_service->check_txn_conflict(
            reinterpret_cast<::google::protobuf::RpcController*>(&begin_txn_cntl),
            &check_txn_conflict_req, &check_txn_conflict_res, nullptr);

    ASSERT_EQ(check_txn_conflict_res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(check_txn_conflict_res.finished(), true);
    ASSERT_EQ(check_txn_conflict_res.conflict_txns_size(), 0);
}

TEST(MetaServiceTest, CheckTxnConflictTest) {
    auto meta_service = get_meta_service();

    const int64_t db_id = 666;
    const int64_t table_id = 777;
    const std::string label = "test_label";
    const std::string cloud_unique_id = "test_cloud_unique_id";
    int64_t txn_id = -1;

    brpc::Controller begin_txn_cntl;
    BeginTxnRequest begin_txn_req;
    BeginTxnResponse begin_txn_res;
    TxnInfoPB txn_info_pb;

    begin_txn_req.set_cloud_unique_id(cloud_unique_id);
    txn_info_pb.set_db_id(db_id);
    txn_info_pb.set_label(label);
    txn_info_pb.add_table_ids(table_id);
    txn_info_pb.set_timeout_ms(36000);
    begin_txn_req.mutable_txn_info()->CopyFrom(txn_info_pb);

    meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&begin_txn_cntl),
                            &begin_txn_req, &begin_txn_res, nullptr);
    ASSERT_EQ(begin_txn_res.status().code(), MetaServiceCode::OK);
    txn_id = begin_txn_res.txn_id();
    ASSERT_GT(txn_id, -1);

    brpc::Controller check_txn_conflict_cntl;
    CheckTxnConflictRequest check_txn_conflict_req;
    CheckTxnConflictResponse check_txn_conflict_res;

    check_txn_conflict_req.set_cloud_unique_id(cloud_unique_id);
    check_txn_conflict_req.set_db_id(db_id);
    check_txn_conflict_req.set_end_txn_id(txn_id + 1);
    check_txn_conflict_req.add_table_ids(table_id);

    // first time to check txn conflict
    meta_service->check_txn_conflict(
            reinterpret_cast<::google::protobuf::RpcController*>(&begin_txn_cntl),
            &check_txn_conflict_req, &check_txn_conflict_res, nullptr);

    ASSERT_EQ(check_txn_conflict_res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(check_txn_conflict_res.finished(), false);
    ASSERT_EQ(check_txn_conflict_res.conflict_txns_size(), 1);
    check_txn_conflict_res.clear_conflict_txns();

    // mock rowset and tablet
    int64_t tablet_id_base = 123456;
    for (int i = 0; i < 5; ++i) {
        create_tablet(meta_service.get(), table_id, 1235, 1236, tablet_id_base + i);
        auto tmp_rowset = create_rowset(txn_id, tablet_id_base + i);
        CreateRowsetResponse res;
        commit_rowset(meta_service.get(), tmp_rowset, res);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    brpc::Controller commit_txn_cntl;
    CommitTxnRequest commit_txn_req;
    commit_txn_req.set_cloud_unique_id(cloud_unique_id);
    commit_txn_req.set_db_id(db_id);
    commit_txn_req.set_txn_id(txn_id);
    CommitTxnResponse commit_txn_res;
    meta_service->commit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&commit_txn_cntl),
                             &commit_txn_req, &commit_txn_res, nullptr);
    ASSERT_EQ(commit_txn_res.status().code(), MetaServiceCode::OK);

    // second time to check txn conflict
    meta_service->check_txn_conflict(
            reinterpret_cast<::google::protobuf::RpcController*>(&check_txn_conflict_cntl),
            &check_txn_conflict_req, &check_txn_conflict_res, nullptr);

    ASSERT_EQ(check_txn_conflict_res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(check_txn_conflict_res.finished(), true);
    ASSERT_EQ(check_txn_conflict_res.conflict_txns_size(), 0);

    {
        std::string running_key = txn_running_key({mock_instance, db_id, txn_id});
        std::string running_value;
        std::unique_ptr<Transaction> txn;
        TxnErrorCode err = meta_service->txn_kv()->create_txn(&txn);
        ASSERT_EQ(err, TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(running_key, &running_value), TxnErrorCode::TXN_KEY_NOT_FOUND);
    }
}

TEST(MetaServiceTest, CheckNotTimeoutTxnConflictTest) {
    auto meta_service = get_meta_service();

    const int64_t db_id = 666;
    const int64_t table_id = 777;
    const std::string label = "test_label";
    const std::string cloud_unique_id = "test_cloud_unique_id";
    int64_t txn_id = -1;

    brpc::Controller begin_txn_cntl;
    BeginTxnRequest begin_txn_req;
    BeginTxnResponse begin_txn_res;
    TxnInfoPB txn_info_pb;

    begin_txn_req.set_cloud_unique_id(cloud_unique_id);
    txn_info_pb.set_db_id(db_id);
    txn_info_pb.set_label(label);
    txn_info_pb.add_table_ids(table_id);
    txn_info_pb.set_timeout_ms(3);
    begin_txn_req.mutable_txn_info()->CopyFrom(txn_info_pb);

    meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&begin_txn_cntl),
                            &begin_txn_req, &begin_txn_res, nullptr);
    ASSERT_EQ(begin_txn_res.status().code(), MetaServiceCode::OK);
    txn_id = begin_txn_res.txn_id();
    ASSERT_GT(txn_id, -1);

    brpc::Controller check_txn_conflict_cntl;
    CheckTxnConflictRequest check_txn_conflict_req;
    CheckTxnConflictResponse check_txn_conflict_res;

    check_txn_conflict_req.set_cloud_unique_id(cloud_unique_id);
    check_txn_conflict_req.set_db_id(db_id);
    check_txn_conflict_req.set_end_txn_id(txn_id + 1);
    check_txn_conflict_req.add_table_ids(table_id);

    // wait txn timeout
    sleep(5);
    // first time to check txn conflict
    meta_service->check_txn_conflict(
            reinterpret_cast<::google::protobuf::RpcController*>(&begin_txn_cntl),
            &check_txn_conflict_req, &check_txn_conflict_res, nullptr);

    ASSERT_EQ(check_txn_conflict_res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(check_txn_conflict_res.finished(), true);
}

TEST(MetaServiceTest, CheckTxnConflictWithAbortLabelTest) {
    int ret = 0;

    auto txn_kv = std::dynamic_pointer_cast<TxnKv>(std::make_shared<MemTxnKv>());
    if (txn_kv != nullptr) {
        ret = txn_kv->init();
        [&] { ASSERT_EQ(ret, 0); }();
    }
    [&] { ASSERT_NE(txn_kv.get(), nullptr); }();

    {
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(txn_kv->create_txn(&txn), TxnErrorCode::TXN_OK);
        txn->remove("\x00", "\xfe"); // This is dangerous if the fdb is not correctly set
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    }

    auto rs = std::make_shared<MockResourceManager>(txn_kv);
    auto rl = std::make_shared<RateLimiter>();
    auto snapshot = std::make_shared<SnapshotManager>(txn_kv);
    auto meta_service = std::make_unique<MetaServiceProxy>(
            std::make_unique<MetaServiceImpl>(txn_kv, rs, rl, snapshot));

    const int64_t db_id = 666;
    const int64_t table_id = 777;
    const std::string label = "test_label";
    const std::string cloud_unique_id = "test_cloud_unique_id";
    int64_t txn_id = -1;

    brpc::Controller begin_txn_cntl;
    BeginTxnRequest begin_txn_req;
    BeginTxnResponse begin_txn_res;
    TxnInfoPB txn_info_pb;

    begin_txn_req.set_cloud_unique_id(cloud_unique_id);
    txn_info_pb.set_db_id(db_id);
    txn_info_pb.set_label(label);
    txn_info_pb.add_table_ids(table_id);
    txn_info_pb.set_timeout_ms(36000);
    begin_txn_req.mutable_txn_info()->CopyFrom(txn_info_pb);

    meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&begin_txn_cntl),
                            &begin_txn_req, &begin_txn_res, nullptr);
    ASSERT_EQ(begin_txn_res.status().code(), MetaServiceCode::OK);
    txn_id = begin_txn_res.txn_id();
    ASSERT_GT(txn_id, -1);

    brpc::Controller check_txn_conflict_cntl;
    CheckTxnConflictRequest check_txn_conflict_req;
    CheckTxnConflictResponse check_txn_conflict_res;

    check_txn_conflict_req.set_cloud_unique_id(cloud_unique_id);
    check_txn_conflict_req.set_db_id(db_id);
    check_txn_conflict_req.set_end_txn_id(txn_id + 1);
    check_txn_conflict_req.add_table_ids(table_id);

    // first time to check txn conflict
    meta_service->check_txn_conflict(
            reinterpret_cast<::google::protobuf::RpcController*>(&begin_txn_cntl),
            &check_txn_conflict_req, &check_txn_conflict_res, nullptr);

    ASSERT_EQ(check_txn_conflict_res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(check_txn_conflict_res.finished(), false);

    std::string running_key;
    std::string running_val;
    txn_running_key({mock_instance, db_id, txn_id}, &running_key);
    {
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(txn_kv->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(running_key, &running_val), TxnErrorCode::TXN_OK);
    }

    brpc::Controller abort_txn_cntl;
    AbortTxnRequest abort_txn_req;
    abort_txn_req.set_cloud_unique_id(cloud_unique_id);
    abort_txn_req.set_db_id(db_id);
    abort_txn_req.set_label(label);
    AbortTxnResponse abort_txn_res;
    meta_service->abort_txn(reinterpret_cast<::google::protobuf::RpcController*>(&abort_txn_cntl),
                            &abort_txn_req, &abort_txn_res, nullptr);
    ASSERT_EQ(abort_txn_res.status().code(), MetaServiceCode::OK);

    // second time to check txn conflict
    meta_service->check_txn_conflict(
            reinterpret_cast<::google::protobuf::RpcController*>(&check_txn_conflict_cntl),
            &check_txn_conflict_req, &check_txn_conflict_res, nullptr);

    ASSERT_EQ(check_txn_conflict_res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(check_txn_conflict_res.finished(), true);

    {
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(txn_kv->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(running_key, &running_val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    }
}

TEST(MetaServiceTest, CleanTxnLabelTest) {
    int ret = 0;
    auto txn_kv = std::dynamic_pointer_cast<TxnKv>(std::make_shared<MemTxnKv>());
    if (txn_kv != nullptr) {
        ret = txn_kv->init();
        [&] { ASSERT_EQ(ret, 0); }();
    }
    [&] { ASSERT_NE(txn_kv.get(), nullptr); }();

    {
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(txn_kv->create_txn(&txn), TxnErrorCode::TXN_OK);
        txn->remove("\x00", "\xfe"); // This is dangerous if the fdb is not correctly set
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    }

    auto rs = std::make_shared<MockResourceManager>(txn_kv);
    auto rl = std::make_shared<RateLimiter>();
    auto snapshot = std::make_shared<SnapshotManager>(txn_kv);
    auto meta_service = std::make_unique<MetaServiceProxy>(
            std::make_unique<MetaServiceImpl>(txn_kv, rs, rl, snapshot));

    // clean txn label by db_id and label
    {
        int64_t txn_id = -1;
        int64_t db_id = 1987211;
        const std::string& label = "test_clean_label";

        {
            brpc::Controller cntl;
            CleanTxnLabelRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_db_id(db_id);
            req.add_labels(label);
            CleanTxnLabelResponse res;
            meta_service->clean_txn_label(
                    reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res,
                    nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }

        // begin txn
        {
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            TxnInfoPB txn_info_pb;
            txn_info_pb.set_db_id(db_id);
            txn_info_pb.set_label(label);
            txn_info_pb.add_table_ids(1234);
            txn_info_pb.set_timeout_ms(36000);
            req.mutable_txn_info()->CopyFrom(txn_info_pb);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            txn_id = res.txn_id();
        }

        const std::string info_key = txn_info_key({mock_instance, db_id, txn_id});
        std::string info_val;

        const std::string label_key = txn_label_key({mock_instance, db_id, label});
        std::string label_val;

        const std::string index_key = txn_index_key({mock_instance, txn_id});
        std::string index_val;

        const std::string running_key = txn_running_key({mock_instance, db_id, txn_id});
        std::string running_val;

        const std::string recycle_key = recycle_txn_key({mock_instance, db_id, txn_id});
        std::string recycle_val;

        {
            std::unique_ptr<Transaction> txn;
            ASSERT_EQ(txn_kv->create_txn(&txn), TxnErrorCode::TXN_OK);
            TxnErrorCode err = txn->get(info_key, &info_val);
            ASSERT_EQ(err, TxnErrorCode::TXN_OK);
            err = txn->get(label_key, &label_val);
            ASSERT_EQ(err, TxnErrorCode::TXN_OK);
            err = txn->get(index_key, &index_val);
            ASSERT_EQ(err, TxnErrorCode::TXN_OK);
            err = txn->get(running_key, &running_val);
            ASSERT_EQ(err, TxnErrorCode::TXN_OK);
            err = txn->get(recycle_key, &recycle_val);
            ASSERT_EQ(err, TxnErrorCode::TXN_KEY_NOT_FOUND);
        }

        // mock rowset and tablet
        int64_t tablet_id_base = 110313131;
        for (int i = 0; i < 2; ++i) {
            create_tablet(meta_service.get(), 1234, 1235, 1236, tablet_id_base + i);
            auto tmp_rowset = create_rowset(txn_id, tablet_id_base + i);
            CreateRowsetResponse res;
            commit_rowset(meta_service.get(), tmp_rowset, res);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }

        // commit txn
        {
            brpc::Controller cntl;
            CommitTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_db_id(db_id);
            req.set_txn_id(txn_id);
            CommitTxnResponse res;
            meta_service->commit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }

        // begin txn
        {
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            TxnInfoPB txn_info_pb;
            txn_info_pb.set_db_id(db_id);
            txn_info_pb.set_label(label);
            txn_info_pb.add_table_ids(1234);
            txn_info_pb.set_timeout_ms(36000);
            req.mutable_txn_info()->CopyFrom(txn_info_pb);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::TXN_LABEL_ALREADY_USED);
        }

        // clean txn label
        {
            brpc::Controller cntl;
            CleanTxnLabelRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            CleanTxnLabelResponse res;
            meta_service->clean_txn_label(
                    reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res,
                    nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        }

        // clean txn label
        {
            brpc::Controller cntl;
            CleanTxnLabelRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_db_id(db_id);
            req.add_labels(label);
            CleanTxnLabelResponse res;
            meta_service->clean_txn_label(
                    reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res,
                    nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }

        {
            std::unique_ptr<Transaction> txn;
            ASSERT_EQ(txn_kv->create_txn(&txn), TxnErrorCode::TXN_OK);
            TxnErrorCode err = txn->get(info_key, &info_val);
            ASSERT_EQ(err, TxnErrorCode::TXN_KEY_NOT_FOUND);
            err = txn->get(label_key, &label_val);
            ASSERT_EQ(err, TxnErrorCode::TXN_KEY_NOT_FOUND);
            err = txn->get(index_key, &index_val);
            ASSERT_EQ(err, TxnErrorCode::TXN_KEY_NOT_FOUND);
            err = txn->get(running_key, &running_val);
            ASSERT_EQ(err, TxnErrorCode::TXN_KEY_NOT_FOUND);
            err = txn->get(recycle_key, &recycle_val);
            ASSERT_EQ(err, TxnErrorCode::TXN_KEY_NOT_FOUND);
        }

        {
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            TxnInfoPB txn_info_pb;
            txn_info_pb.set_db_id(db_id);
            txn_info_pb.set_label(label);
            txn_info_pb.add_table_ids(1234);
            txn_info_pb.set_timeout_ms(36000);
            req.mutable_txn_info()->CopyFrom(txn_info_pb);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            txn_id = res.txn_id();
        }

        // abort txn
        {
            brpc::Controller cntl;
            AbortTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            ASSERT_GT(txn_id, 0);
            req.set_txn_id(txn_id);
            req.set_reason("test");
            AbortTxnResponse res;
            meta_service->abort_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(res.txn_info().status(), TxnStatusPB::TXN_STATUS_ABORTED);
        }

        {
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            TxnInfoPB txn_info_pb;
            txn_info_pb.set_db_id(db_id);
            txn_info_pb.set_label(label);
            txn_info_pb.add_table_ids(1234);
            txn_info_pb.set_timeout_ms(36000);
            req.mutable_txn_info()->CopyFrom(txn_info_pb);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            txn_id = res.txn_id();
        }

        // clean txn label
        {
            brpc::Controller cntl;
            CleanTxnLabelRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_db_id(db_id);
            req.add_labels(label);
            CleanTxnLabelResponse res;
            meta_service->clean_txn_label(
                    reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res,
                    nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }

        // abort txn
        {
            brpc::Controller cntl;
            AbortTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            ASSERT_GT(txn_id, 0);
            req.set_txn_id(txn_id);
            req.set_reason("test");
            AbortTxnResponse res;
            meta_service->abort_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(res.txn_info().status(), TxnStatusPB::TXN_STATUS_ABORTED);
        }

        // clean txn label
        {
            brpc::Controller cntl;
            CleanTxnLabelRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_db_id(db_id);
            req.add_labels(label);
            CleanTxnLabelResponse res;
            meta_service->clean_txn_label(
                    reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res,
                    nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }

        {
            const std::string info_key = txn_info_key({mock_instance, db_id, txn_id});
            std::string info_val;

            const std::string label_key = txn_label_key({mock_instance, db_id, label});
            std::string label_val;

            const std::string index_key = txn_index_key({mock_instance, txn_id});
            std::string index_val;

            const std::string running_key = txn_running_key({mock_instance, db_id, txn_id});
            std::string running_val;

            const std::string recycle_key = recycle_txn_key({mock_instance, db_id, txn_id});
            std::string recycle_val;

            std::unique_ptr<Transaction> txn;
            ASSERT_EQ(txn_kv->create_txn(&txn), TxnErrorCode::TXN_OK);
            TxnErrorCode ret = txn->get(info_key, &info_val);
            ASSERT_EQ(ret, TxnErrorCode::TXN_KEY_NOT_FOUND);
            ret = txn->get(label_key, &label_val);
            ASSERT_EQ(ret, TxnErrorCode::TXN_KEY_NOT_FOUND);
            ret = txn->get(index_key, &index_val);
            ASSERT_EQ(ret, TxnErrorCode::TXN_KEY_NOT_FOUND);
            ret = txn->get(running_key, &running_val);
            ASSERT_EQ(ret, TxnErrorCode::TXN_KEY_NOT_FOUND);
            ret = txn->get(recycle_key, &recycle_val);
            ASSERT_EQ(ret, TxnErrorCode::TXN_KEY_NOT_FOUND);
        }
    }
    // clean txn label only by db_id
    {
        int64_t db_id = 1987211123;
        const std::string& label = "test_clean_label";

        TxnInfoPB txn_info_pb;
        txn_info_pb.set_db_id(db_id);
        txn_info_pb.add_table_ids(1234);
        txn_info_pb.set_timeout_ms(36000);
        BeginTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");

        //clean not exist label
        {
            brpc::Controller cntl;
            CleanTxnLabelRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_db_id(db_id);
            CleanTxnLabelResponse res;
            meta_service->clean_txn_label(
                    reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res,
                    nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }

        // inject internal_clean_label err = TXN_CONFLICT
        {
            auto sp = SyncPoint::get_instance();
            sp->set_call_back("internal_clean_label:err", [&](auto&& args) {
                auto* err = try_any_cast<TxnErrorCode*>(args[0]);
                *err = TxnErrorCode::TXN_CONFLICT;
            });
            sp->enable_processing();
            int64_t txn_id = -1;
            for (int i = 100; i < 101; i++) {
                {
                    std::stringstream label_ss;
                    label_ss << label << i;
                    brpc::Controller cntl;
                    txn_info_pb.set_label(label_ss.str());
                    req.mutable_txn_info()->CopyFrom(txn_info_pb);
                    BeginTxnResponse res;
                    meta_service->begin_txn(
                            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res,
                            nullptr);
                    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
                    txn_id = res.txn_id();
                }

                {
                    // mock rowset and tablet
                    int64_t tablet_id_base = 110313131;
                    for (int i = 0; i < 1; ++i) {
                        create_tablet(meta_service.get(), 1234, 1235, 1236, tablet_id_base + i);
                        auto tmp_rowset = create_rowset(txn_id, tablet_id_base + i);
                        CreateRowsetResponse res;
                        commit_rowset(meta_service.get(), tmp_rowset, res);
                        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
                    }
                }

                // commit txn
                {
                    brpc::Controller cntl;
                    CommitTxnRequest req;
                    req.set_cloud_unique_id("test_cloud_unique_id");
                    req.set_db_id(db_id);
                    req.set_txn_id(txn_id);
                    CommitTxnResponse res;
                    meta_service->commit_txn(
                            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res,
                            nullptr);
                    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
                }
            }

            {
                brpc::Controller cntl;
                CleanTxnLabelRequest req;
                req.set_cloud_unique_id("test_cloud_unique_id");
                req.set_db_id(db_id);
                CleanTxnLabelResponse res;
                meta_service->clean_txn_label(
                        reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res,
                        nullptr);
                ASSERT_EQ(res.status().code(), MetaServiceCode::KV_TXN_CONFLICT);
            }
            sp->clear_all_call_backs();
            sp->clear_trace();
            sp->disable_processing();
        }

        // create 12 committed txns and clean label by id
        {
            auto sp = SyncPoint::get_instance();
            sp->set_call_back("clean_txn_label:limit", [](auto&& args) {
                int* limit = try_any_cast<int*>(args[0]);
                *limit = 5;
            });
            sp->enable_processing();

            int64_t txn_id = -1;
            for (int i = 0; i < 12; i++) {
                {
                    std::stringstream label_ss;
                    label_ss << label << i;
                    brpc::Controller cntl;
                    txn_info_pb.set_label(label_ss.str());
                    req.mutable_txn_info()->CopyFrom(txn_info_pb);
                    BeginTxnResponse res;
                    meta_service->begin_txn(
                            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res,
                            nullptr);
                    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
                    txn_id = res.txn_id();
                }

                {
                    // mock rowset and tablet
                    int64_t tablet_id_base = 110313131;
                    for (int i = 0; i < 1; ++i) {
                        create_tablet(meta_service.get(), 1234, 1235, 1236, tablet_id_base + i);
                        auto tmp_rowset = create_rowset(txn_id, tablet_id_base + i);
                        CreateRowsetResponse res;
                        commit_rowset(meta_service.get(), tmp_rowset, res);
                        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
                    }
                }

                // commit txn
                {
                    brpc::Controller cntl;
                    CommitTxnRequest req;
                    req.set_cloud_unique_id("test_cloud_unique_id");
                    req.set_db_id(db_id);
                    req.set_txn_id(txn_id);
                    CommitTxnResponse res;
                    meta_service->commit_txn(
                            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res,
                            nullptr);
                    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
                }
            }

            {
                brpc::Controller cntl;
                CleanTxnLabelRequest req;
                req.set_cloud_unique_id("test_cloud_unique_id");
                req.set_db_id(db_id);
                CleanTxnLabelResponse res;
                meta_service->clean_txn_label(
                        reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res,
                        nullptr);
                ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            }
            sp->clear_all_call_backs();
            sp->clear_trace();
            sp->disable_processing();
        }
    }
}

TEST(MetaServiceTest, GetTxnTest) {
    int ret = 0;
    auto txn_kv = std::dynamic_pointer_cast<TxnKv>(std::make_shared<MemTxnKv>());
    if (txn_kv != nullptr) {
        ret = txn_kv->init();
        [&] { ASSERT_EQ(ret, 0); }();
    }
    [&] { ASSERT_NE(txn_kv.get(), nullptr); }();

    {
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(txn_kv->create_txn(&txn), TxnErrorCode::TXN_OK);
        txn->remove("\x00", "\xfe"); // This is dangerous if the fdb is not correctly set
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    }

    auto rs = std::make_shared<MockResourceManager>(txn_kv);
    auto rl = std::make_shared<RateLimiter>();
    auto snapshot = std::make_shared<SnapshotManager>(txn_kv);
    auto meta_service = std::make_unique<MetaServiceProxy>(
            std::make_unique<MetaServiceImpl>(txn_kv, rs, rl, snapshot));

    {
        int64_t txn_id = -1;
        int64_t db_id = 34521431231;
        const std::string& label = "test_get_txn";

        // begin txn
        {
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            TxnInfoPB txn_info_pb;
            txn_info_pb.set_db_id(db_id);
            txn_info_pb.set_label(label);
            txn_info_pb.add_table_ids(1234);
            txn_info_pb.set_timeout_ms(36000);
            req.mutable_txn_info()->CopyFrom(txn_info_pb);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            txn_id = res.txn_id();
        }

        {
            brpc::Controller cntl;
            GetTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_db_id(db_id);
            req.set_txn_id(-1);
            GetTxnResponse res;
            meta_service->get_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                  &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        }

        {
            brpc::Controller cntl;
            GetTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_db_id(db_id);
            req.set_txn_id(txn_id);
            GetTxnResponse res;
            meta_service->get_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                  &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }

        {
            brpc::Controller cntl;
            GetTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_txn_id(txn_id);
            GetTxnResponse res;
            meta_service->get_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                  &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }
    }
}
//

TEST(MetaServiceTest, CopyJobTest) {
    auto meta_service = get_meta_service();
    brpc::Controller cntl;
    auto cloud_unique_id = "test_cloud_unique_id";
    auto stage_id = "test_stage_id";
    int64_t table_id = 100;
    std::string instance_id = "copy_job_test_instance_id";

    [[maybe_unused]] auto sp = SyncPoint::get_instance();
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
    };
    sp->set_call_back("get_instance_id", [&](auto&& args) {
        auto* ret = try_any_cast_ret<std::string>(args);
        ret->first = instance_id;
        ret->second = true;
    });
    sp->enable_processing();

    // generate a begin copy request
    BeginCopyRequest begin_copy_request;
    begin_copy_request.set_cloud_unique_id(cloud_unique_id);
    begin_copy_request.set_stage_id(stage_id);
    begin_copy_request.set_stage_type(StagePB::EXTERNAL);
    begin_copy_request.set_table_id(table_id);
    begin_copy_request.set_copy_id("test_copy_id");
    begin_copy_request.set_group_id(0);
    begin_copy_request.set_start_time_ms(200);
    begin_copy_request.set_timeout_time_ms(300);
    for (int i = 0; i < 20; ++i) {
        ObjectFilePB object_file_pb;
        object_file_pb.set_relative_path("obj_" + std::to_string(i));
        object_file_pb.set_etag("obj_" + std::to_string(i) + "_etag");
        begin_copy_request.add_object_files()->CopyFrom(object_file_pb);
    }

    // generate a finish copy request
    FinishCopyRequest finish_copy_request;
    finish_copy_request.set_cloud_unique_id(cloud_unique_id);
    finish_copy_request.set_stage_id(stage_id);
    finish_copy_request.set_stage_type(StagePB::EXTERNAL);
    finish_copy_request.set_table_id(table_id);
    finish_copy_request.set_copy_id("test_copy_id");
    finish_copy_request.set_group_id(0);
    finish_copy_request.set_action(FinishCopyRequest::COMMIT);

    // generate a get copy files request
    GetCopyFilesRequest get_copy_file_req;
    get_copy_file_req.set_cloud_unique_id(cloud_unique_id);
    get_copy_file_req.set_stage_id(stage_id);
    get_copy_file_req.set_table_id(table_id);

    // generate a get copy job request
    GetCopyJobRequest get_copy_job_request;
    get_copy_job_request.set_cloud_unique_id(cloud_unique_id);
    get_copy_job_request.set_stage_id(stage_id);
    get_copy_job_request.set_table_id(table_id);
    get_copy_job_request.set_copy_id("test_copy_id");
    get_copy_job_request.set_group_id(0);

    // get copy job
    {
        GetCopyJobResponse res;
        meta_service->get_copy_job(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                   &get_copy_job_request, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.has_copy_job(), false);
    }
    // begin copy
    {
        BeginCopyResponse res;
        meta_service->begin_copy(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                 &begin_copy_request, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.filtered_object_files_size(), 20);
    }
    // get copy files
    {
        GetCopyFilesResponse res;
        meta_service->get_copy_files(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &get_copy_file_req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.object_files_size(), 20);
    }
    // get copy job
    {
        GetCopyJobResponse res;
        meta_service->get_copy_job(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                   &get_copy_job_request, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.copy_job().object_files().size(), 20);
    }
    // begin copy with duplicate files
    {
        begin_copy_request.set_copy_id("test_copy_id_1");
        begin_copy_request.clear_object_files();
        for (int i = 15; i < 30; ++i) {
            ObjectFilePB object_file_pb;
            object_file_pb.set_relative_path("obj_" + std::to_string(i));
            object_file_pb.set_etag("obj_" + std::to_string(i) + "_etag");
            begin_copy_request.add_object_files()->CopyFrom(object_file_pb);
        }

        BeginCopyResponse res;
        meta_service->begin_copy(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                 &begin_copy_request, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.filtered_object_files_size(), 10);
    }
    // get copy files
    {
        GetCopyFilesResponse res;
        meta_service->get_copy_files(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &get_copy_file_req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.object_files_size(), 30);
    }
    // finish the first copy job
    {
        FinishCopyResponse res;
        meta_service->finish_copy(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                  &finish_copy_request, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }
    // get copy files
    {
        GetCopyFilesResponse res;
        meta_service->get_copy_files(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &get_copy_file_req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.object_files_size(), 30);
    }
    // abort the second copy job
    {
        finish_copy_request.set_copy_id("test_copy_id_1");
        finish_copy_request.set_action(FinishCopyRequest::ABORT);

        FinishCopyResponse res;
        meta_service->finish_copy(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                  &finish_copy_request, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }
    // get copy files
    {
        GetCopyFilesResponse res;
        meta_service->get_copy_files(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &get_copy_file_req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.object_files_size(), 20);
    }
    {
        // begin a copy job whose files are all loaded, the copy job key should not be created
        begin_copy_request.set_copy_id("tmp_id");
        begin_copy_request.clear_object_files();
        for (int i = 0; i < 20; ++i) {
            ObjectFilePB object_file_pb;
            object_file_pb.set_relative_path("obj_" + std::to_string(i));
            object_file_pb.set_etag("obj_" + std::to_string(i) + "_etag");
            begin_copy_request.add_object_files()->CopyFrom(object_file_pb);
        }
        BeginCopyResponse res;
        meta_service->begin_copy(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                 &begin_copy_request, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.filtered_object_files_size(), 0);
        // get copy job
        get_copy_job_request.set_copy_id("tmp_id");
        GetCopyJobResponse res2;
        meta_service->get_copy_job(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                   &get_copy_job_request, &res2, nullptr);
        ASSERT_EQ(res2.status().code(), MetaServiceCode::OK);
        ASSERT_FALSE(res2.has_copy_job());
    }
    // scan fdb
    {
        std::unique_ptr<Transaction> txn;
        std::string get_val;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        // 20 copy files
        {
            CopyFileKeyInfo key_info0 {instance_id, stage_id, table_id, "", ""};
            CopyFileKeyInfo key_info1 {instance_id, stage_id, table_id + 1, "", ""};
            std::string key0;
            std::string key1;
            copy_file_key(key_info0, &key0);
            copy_file_key(key_info1, &key1);
            std::unique_ptr<RangeGetIterator> it;
            ASSERT_EQ(txn->get(key0, key1, &it), TxnErrorCode::TXN_OK);
            int file_cnt = 0;
            do {
                ASSERT_EQ(txn->get(key0, key1, &it), TxnErrorCode::TXN_OK);
                while (it->has_next()) {
                    auto [k, v] = it->next();
                    CopyFilePB copy_file;
                    ASSERT_TRUE(copy_file.ParseFromArray(v.data(), v.size()));
                    ASSERT_EQ(copy_file.copy_id(), "test_copy_id");
                    ++file_cnt;
                    if (!it->has_next()) {
                        key0 = k;
                    }
                }
                key0.push_back('\x00');
            } while (it->more());
            ASSERT_EQ(file_cnt, 20);
        }
        // 1 copy job with finish status
        {
            CopyJobKeyInfo key_info0 {instance_id, stage_id, table_id, "", 0};
            CopyJobKeyInfo key_info1 {instance_id, stage_id, table_id + 1, "", 0};
            std::string key0;
            std::string key1;
            copy_job_key(key_info0, &key0);
            copy_job_key(key_info1, &key1);
            std::unique_ptr<RangeGetIterator> it;
            int job_cnt = 0;
            do {
                ASSERT_EQ(txn->get(key0, key1, &it), TxnErrorCode::TXN_OK);
                while (it->has_next()) {
                    auto [k, v] = it->next();
                    CopyJobPB copy_job;
                    ASSERT_EQ(copy_job.ParseFromArray(v.data(), v.size()), true);
                    ASSERT_EQ(copy_job.object_files_size(), 20);
                    ASSERT_EQ(copy_job.job_status(), CopyJobPB::FINISH);
                    ++job_cnt;
                    if (!it->has_next()) {
                        key0 = k;
                    }
                }
                key0.push_back('\x00');
            } while (it->more());
            ASSERT_EQ(job_cnt, 1);
        }
    }
}

TEST(MetaServiceTest, FilterCopyFilesTest) {
    auto meta_service = get_meta_service();
    brpc::Controller cntl;
    auto cloud_unique_id = "test_cloud_unique_id";
    std::string instance_id = "stage_test_instance_id";
    auto stage_id = "test_stage_id";
    int64_t table_id = 100;
    [[maybe_unused]] auto sp = SyncPoint::get_instance();
    sp->set_call_back("get_instance_id", [&](auto&& args) {
        auto* ret = try_any_cast_ret<std::string>(args);
        ret->first = instance_id;
        ret->second = true;
    });
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "test";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });
    sp->set_call_back("decrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* key = try_any_cast<std::string*>(args[0]);
        *key = "test";
        auto* ret = try_any_cast<int*>(args[1]);
        *ret = 0;
    });
    sp->enable_processing();

    FilterCopyFilesRequest request;
    request.set_cloud_unique_id(cloud_unique_id);
    request.set_stage_id(stage_id);
    request.set_table_id(table_id);
    for (int i = 0; i < 10; ++i) {
        ObjectFilePB object_file;
        object_file.set_relative_path("file" + std::to_string(i));
        object_file.set_etag("etag" + std::to_string(i));
        request.add_object_files()->CopyFrom(object_file);
    }

    // all files are not loaded
    {
        FilterCopyFilesResponse res;
        meta_service->filter_copy_files(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                        &request, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.object_files().size(), 10);
    }

    // some files are loaded
    {
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        for (int i = 0; i < 4; ++i) {
            CopyFileKeyInfo key_info {instance_id, stage_id, table_id, "file" + std::to_string(i),
                                      "etag" + std::to_string(i)};
            std::string key;
            copy_file_key(key_info, &key);
            CopyFilePB copy_file;
            copy_file.set_copy_id("test_copy_id");
            std::string val;
            copy_file.SerializeToString(&val);
            txn->put(key, val);
        }
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
        FilterCopyFilesResponse res;
        meta_service->filter_copy_files(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                        &request, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.object_files().size(), 6);
        ASSERT_EQ(res.object_files().at(0).relative_path(), "file4");
    }

    // all files are loaded
    {
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        for (int i = 4; i < 10; ++i) {
            CopyFileKeyInfo key_info {instance_id, stage_id, table_id, "file" + std::to_string(i),
                                      "etag" + std::to_string(i)};
            std::string key;
            copy_file_key(key_info, &key);
            CopyFilePB copy_file;
            copy_file.set_copy_id("test_copy_id");
            std::string val;
            copy_file.SerializeToString(&val);
            txn->put(key, val);
        }
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
        FilterCopyFilesResponse res;
        meta_service->filter_copy_files(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                        &request, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.object_files().size(), 0);
    }
}

extern std::vector<std::pair<int64_t, int64_t>> calc_sync_versions(
        int64_t req_bc_cnt, int64_t bc_cnt, int64_t req_cc_cnt, int64_t cc_cnt, int64_t req_cp,
        int64_t cp, int64_t req_fc_cnt, int64_t fc_cnt, int64_t req_start, int64_t req_end);

TEST(MetaServiceTest, CalcSyncVersionsTest) {
    using Versions = std::vector<std::pair<int64_t, int64_t>>;
    // * no compaction happened
    // req_cc_cnt == ms_cc_cnt && req_bc_cnt == ms_bc_cnt && req_cp == ms_cp
    // BE  [=][=][=][=][=====][=][=]<.......>
    //                  ^~~~~ req_cp
    // BE  [=][=][=][=][=====][=][=][=][=][=][=]
    //                  ^~~~~ ms_cp
    //                               ^_____^ versions_return: [req_start, req_end]
    {
        auto [req_bc_cnt, bc_cnt] = std::tuple {0, 0};
        auto [req_cc_cnt, cc_cnt] = std::tuple {1, 1};
        auto [req_cp, cp] = std::tuple {5, 5};
        auto [req_start, req_end] = std::tuple {8, 12};
        auto versions = calc_sync_versions(req_bc_cnt, bc_cnt, req_cc_cnt, cc_cnt, req_cp, cp, 0, 0,
                                           req_start, req_end);
        ASSERT_EQ(versions, (Versions {{8, 12}}));
    }
    // * only one CC happened and CP changed
    // req_cc_cnt == ms_cc_cnt - 1 && req_bc_cnt == ms_bc_cnt && req_cp < ms_cp
    // BE  [=][=][=][=][=====][=][=]<.......>
    //                  ^~~~~ req_cp
    // MS  [=][=][=][=][xxxxxxxxxxxxxx][=======][=][=]
    //                                  ^~~~~~~ ms_cp
    //                  ^__________________^ versions_return: [req_cp, ms_cp - 1] v [req_start, req_end]
    {
        auto [req_bc_cnt, bc_cnt] = std::tuple {0, 0};
        auto [req_cc_cnt, cc_cnt] = std::tuple {1, 2};
        auto [req_cp, cp] = std::tuple {5, 10};
        auto [req_start, req_end] = std::tuple {8, 12};
        auto versions = calc_sync_versions(req_bc_cnt, bc_cnt, req_cc_cnt, cc_cnt, req_cp, cp, 0, 0,
                                           req_start, req_end);
        ASSERT_EQ(versions, (Versions {{5, 12}})); // [5, 9] v [8, 12]
    }
    {
        auto [req_bc_cnt, bc_cnt] = std::tuple {0, 0};
        auto [req_cc_cnt, cc_cnt] = std::tuple {1, 2};
        auto [req_cp, cp] = std::tuple {5, 15};
        auto [req_start, req_end] = std::tuple {8, 12};
        auto versions = calc_sync_versions(req_bc_cnt, bc_cnt, req_cc_cnt, cc_cnt, req_cp, cp, 0, 0,
                                           req_start, req_end);
        ASSERT_EQ(versions, (Versions {{5, 14}})); // [5, 14] v [8, 12]
    }
    // * only one CC happened and CP remain unchanged
    // req_cc_cnt == ms_cc_cnt - 1 && req_bc_cnt == ms_bc_cnt && req_cp == ms_cp
    // BE  [=][=][=][=][=====][=][=]<.......>
    //                  ^~~~~ req_cp
    // MS  [=][=][=][=][xxxxxxxxxxxxxx][=][=][=][=][=]
    //                  ^~~~~~~~~~~~~~ ms_cp
    //                  ^__________________^ versions_return: [req_cp, max] v [req_start, req_end]
    //
    {
        auto [req_bc_cnt, bc_cnt] = std::tuple {0, 0};
        auto [req_cc_cnt, cc_cnt] = std::tuple {1, 2};
        auto [req_cp, cp] = std::tuple {5, 5};
        auto [req_start, req_end] = std::tuple {8, 12};
        auto versions = calc_sync_versions(req_bc_cnt, bc_cnt, req_cc_cnt, cc_cnt, req_cp, cp, 0, 0,
                                           req_start, req_end);
        ASSERT_EQ(versions, (Versions {{5, INT64_MAX - 1}})); // [5, max] v [8, 12]
    }
    // * more than one CC happened and CP remain unchanged
    // req_cc_cnt < ms_cc_cnt - 1 && req_bc_cnt == ms_bc_cnt && req_cp == ms_cp
    // BE  [=][=][=][=][=====][=][=]<.......>
    //                  ^~~~~ req_cp
    // MS  [=][=][=][=][xxxxxxxxxxxxxx][xxxxxxx][=][=]
    //                  ^~~~~~~~~~~~~~ ms_cp
    //                  ^_____________________^ versions_return: [req_cp, max] v [req_start, req_end]
    {
        auto [req_bc_cnt, bc_cnt] = std::tuple {0, 0};
        auto [req_cc_cnt, cc_cnt] = std::tuple {1, 3};
        auto [req_cp, cp] = std::tuple {5, 5};
        auto [req_start, req_end] = std::tuple {8, 12};
        auto versions = calc_sync_versions(req_bc_cnt, bc_cnt, req_cc_cnt, cc_cnt, req_cp, cp, 0, 0,
                                           req_start, req_end);
        ASSERT_EQ(versions, (Versions {{5, INT64_MAX - 1}})); // [5, max] v [8, 12]
    }
    // * more than one CC happened and CP changed
    // BE  [=][=][=][=][=====][=][=]
    //                  ^~~~~ req_cp
    // MS  [=][=][=][=][xxxxxxxxxxxxxx][xxxxxxx][=][=]
    //                                  ^~~~~~~ ms_cp
    //                  ^_____________________^ related_versions: [req_cp, max] v [req_start, req_end]
    {
        auto [req_bc_cnt, bc_cnt] = std::tuple {0, 0};
        auto [req_cc_cnt, cc_cnt] = std::tuple {1, 3};
        auto [req_cp, cp] = std::tuple {5, 15};
        auto [req_start, req_end] = std::tuple {8, 12};
        auto versions = calc_sync_versions(req_bc_cnt, bc_cnt, req_cc_cnt, cc_cnt, req_cp, cp, 0, 0,
                                           req_start, req_end);
        ASSERT_EQ(versions, (Versions {{5, INT64_MAX - 1}})); // [5, max] v [8, 12]
    }
    // * for any BC happended
    // req_bc_cnt < ms_bc_cnt
    // BE  [=][=][=][=][=====][=][=]<.......>
    //                  ^~~~~ req_cp
    // MS  [xxxxxxxxxx][xxxxxxxxxxxxxx][=======][=][=]
    //                                  ^~~~~~~ ms_cp
    //     ^_________________________^ versions_return: [0, ms_cp - 1] v versions_return_in_above_case
    {
        auto [req_bc_cnt, bc_cnt] = std::tuple {0, 1};
        auto [req_cc_cnt, cc_cnt] = std::tuple {1, 1};
        auto [req_cp, cp] = std::tuple {5, 5};
        auto [req_start, req_end] = std::tuple {8, 12};
        auto versions = calc_sync_versions(req_bc_cnt, bc_cnt, req_cc_cnt, cc_cnt, req_cp, cp, 0, 0,
                                           req_start, req_end);
        ASSERT_EQ(versions, (Versions {{0, 4}, {8, 12}}));
    }
    {
        auto [req_bc_cnt, bc_cnt] = std::tuple {0, 1};
        auto [req_cc_cnt, cc_cnt] = std::tuple {1, 1};
        auto [req_cp, cp] = std::tuple {8, 8};
        auto [req_start, req_end] = std::tuple {8, 12};
        auto versions = calc_sync_versions(req_bc_cnt, bc_cnt, req_cc_cnt, cc_cnt, req_cp, cp, 0, 0,
                                           req_start, req_end);
        ASSERT_EQ(versions, (Versions {{0, 12}})); // [0, 7] v [8, 12]
    }
    {
        auto [req_bc_cnt, bc_cnt] = std::tuple {0, 1};
        auto [req_cc_cnt, cc_cnt] = std::tuple {1, 2};
        auto [req_cp, cp] = std::tuple {5, 10};
        auto [req_start, req_end] = std::tuple {8, 12};
        auto versions = calc_sync_versions(req_bc_cnt, bc_cnt, req_cc_cnt, cc_cnt, req_cp, cp, 0, 0,
                                           req_start, req_end);
        ASSERT_EQ(versions, (Versions {{0, 12}})); // [0, 4] v [5, 9] v [8, 12]
    }
    {
        auto [req_bc_cnt, bc_cnt] = std::tuple {0, 1};
        auto [req_cc_cnt, cc_cnt] = std::tuple {1, 2};
        auto [req_cp, cp] = std::tuple {5, 15};
        auto [req_start, req_end] = std::tuple {8, 12};
        auto versions = calc_sync_versions(req_bc_cnt, bc_cnt, req_cc_cnt, cc_cnt, req_cp, cp, 0, 0,
                                           req_start, req_end);
        ASSERT_EQ(versions, (Versions {{0, 14}})); // [0, 4] v [5, 14] v [8, 12]
    }
    {
        auto [req_bc_cnt, bc_cnt] = std::tuple {0, 1};
        auto [req_cc_cnt, cc_cnt] = std::tuple {1, 2};
        auto [req_cp, cp] = std::tuple {5, 5};
        auto [req_start, req_end] = std::tuple {8, 12};
        auto versions = calc_sync_versions(req_bc_cnt, bc_cnt, req_cc_cnt, cc_cnt, req_cp, cp, 0, 0,
                                           req_start, req_end);
        // [0, 4] v [5, max] v [8, 12]
        ASSERT_EQ(versions, (Versions {{0, INT64_MAX - 1}}));
    }

    {
        // when there exists full compaction, we can't optimize by "* only one CC happened and CP changed"

        // * one CC happened and CP changed, and full compaction happened
        // BE  [=][=][=][=][=][=][=][=][=][=]
        //                  ^~~~~ req_cp
        // MS  [xxxxxxxxxxxxxx][xxxxxxxxxxxxxx][=======][=][=]
        //                      ^~~~~~~ ms_cp
        //                  ^___________________________^ related_versions: [req_cp, max]
        //
        auto [req_bc_cnt, bc_cnt] = std::tuple {0, 1};
        auto [req_cc_cnt, cc_cnt] = std::tuple {1, 2};
        auto [req_cp, cp] = std::tuple {4, 7};
        auto [req_start, req_end] = std::tuple {9, 12};
        auto versions = calc_sync_versions(req_bc_cnt, bc_cnt, req_cc_cnt, cc_cnt, req_cp, cp, 0, 1,
                                           req_start, req_end);
        ASSERT_EQ(versions, (Versions {{0, INT64_MAX - 1}}));
    }

    {
        // abnormal case:
        auto [req_bc_cnt, bc_cnt] = std::tuple {0, 1};
        auto [req_cc_cnt, cc_cnt] = std::tuple {1, 2};
        auto [req_cp, cp] = std::tuple {4, 7};
        auto [req_start, req_end] = std::tuple {9, 12};
        auto versions = calc_sync_versions(req_bc_cnt, bc_cnt, req_cc_cnt, cc_cnt, req_cp, cp, 0, 0,
                                           req_start, req_end);
        // when not considering full compaction, the returned versions is wrong becasue rowsets in [7-8] are missed
        ASSERT_EQ(versions, (Versions {{0, 6}, {9, 12}}));
    }
}

TEST(MetaServiceTest, StageTest) {
    auto meta_service = get_meta_service();
    brpc::Controller cntl;
    auto cloud_unique_id = "test_cloud_unique_id";
    std::string instance_id = "stage_test_instance_id";
    [[maybe_unused]] auto sp = SyncPoint::get_instance();
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
    };
    sp->set_call_back("get_instance_id", [&](auto&& args) {
        auto* ret = try_any_cast_ret<std::string>(args);
        ret->first = instance_id;
        ret->second = true;
    });
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "test";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });
    sp->set_call_back("decrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* key = try_any_cast<std::string*>(args[0]);
        *key = "test";
        auto* ret = try_any_cast<int*>(args[1]);
        *ret = 0;
    });
    sp->enable_processing();

    ObjectStoreInfoPB obj;
    obj.set_ak("123");
    obj.set_sk("321");
    obj.set_bucket("456");
    obj.set_prefix("654");
    obj.set_endpoint("789");
    obj.set_region("987");
    obj.set_external_endpoint("888");
    obj.set_provider(ObjectStoreInfoPB::BOS);

    RamUserPB ram_user;
    ram_user.set_user_id("test_user_id");
    ram_user.set_ak("test_ak");
    ram_user.set_sk("test_sk");
    EncryptionInfoPB encry_info;
    encry_info.set_encryption_method("encry_method_test");
    encry_info.set_key_id(1111);
    ram_user.mutable_encryption_info()->CopyFrom(encry_info);

    // create instance
    {
        CreateInstanceRequest req;
        req.set_instance_id(instance_id);
        req.set_user_id("test_user");
        req.set_name("test_name");
        req.mutable_ram_user()->CopyFrom(ram_user);
        req.mutable_obj_info()->CopyFrom(obj);

        CreateInstanceResponse res;
        meta_service->create_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                      &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // test create and get internal stage
    {
        // get a non-existent internal stage
        GetStageRequest get_stage_req;
        GetStageResponse res;
        // no cloud_unique_id
        meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                &get_stage_req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);

        get_stage_req.set_cloud_unique_id(cloud_unique_id);
        // no instance_id
        sp->set_call_back("get_instance_id", [&](auto&& args) {
            auto* ret = try_any_cast_ret<std::string>(args);
            ret->first = "";
            ret->second = true;
        });
        meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                &get_stage_req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        sp->clear_call_back("get_instance_id");
        sp->set_call_back("get_instance_id", [&](auto&& args) {
            auto* ret = try_any_cast_ret<std::string>(args);
            ret->first = instance_id;
            ret->second = true;
        });

        // no stage type
        meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                &get_stage_req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        get_stage_req.set_type(StagePB::INTERNAL);

        // no internal stage user name
        meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                &get_stage_req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        get_stage_req.set_mysql_user_name("root");

        // no internal stage user id
        meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                &get_stage_req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        get_stage_req.set_mysql_user_id("root_id");
        meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                &get_stage_req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::STAGE_NOT_FOUND);

        // create an internal stage
        CreateStageRequest create_stage_request;
        StagePB stage;
        stage.set_type(StagePB::INTERNAL);
        stage.add_mysql_user_name("root");
        stage.add_mysql_user_id("root_id");
        stage.set_stage_id("internal_stage_id");
        create_stage_request.set_cloud_unique_id(cloud_unique_id);
        create_stage_request.mutable_stage()->CopyFrom(stage);
        CreateStageResponse create_stage_response;
        meta_service->create_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                   &create_stage_request, &create_stage_response, nullptr);
        ASSERT_EQ(create_stage_response.status().code(), MetaServiceCode::OK);

        // get existent internal stage
        GetStageResponse res2;
        meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                &get_stage_req, &res2, nullptr);
        ASSERT_EQ(res2.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(1, res2.stage().size());

        // can't find user id's stage
        GetStageResponse res3;
        get_stage_req.set_mysql_user_id("not_root_id_exist");
        meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                &get_stage_req, &res3, nullptr);
        ASSERT_EQ(res3.status().code(), MetaServiceCode::STATE_ALREADY_EXISTED_FOR_USER);
        ASSERT_EQ(1, res3.stage().size());

        // drop internal stage
        DropStageRequest drop_stage_request;
        drop_stage_request.set_cloud_unique_id(cloud_unique_id);
        drop_stage_request.set_type(StagePB::INTERNAL);
        drop_stage_request.set_mysql_user_id("root_id");
        drop_stage_request.set_reason("Drop");
        DropStageResponse drop_stage_response;
        meta_service->drop_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                 &drop_stage_request, &drop_stage_response, nullptr);
        ASSERT_EQ(drop_stage_response.status().code(), MetaServiceCode::OK);
        // scan fdb has recycle_stage key
        {
            RecycleStageKeyInfo key_info0 {instance_id, ""};
            RecycleStageKeyInfo key_info1 {instance_id, "{"};
            std::string key0;
            std::string key1;
            recycle_stage_key(key_info0, &key0);
            recycle_stage_key(key_info1, &key1);
            std::unique_ptr<Transaction> txn;
            std::string get_val;
            ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
            std::unique_ptr<RangeGetIterator> it;
            ASSERT_EQ(txn->get(key0, key1, &it), TxnErrorCode::TXN_OK);
            int stage_cnt = 0;
            do {
                ASSERT_EQ(txn->get(key0, key1, &it), TxnErrorCode::TXN_OK);
                while (it->has_next()) {
                    auto [k, v] = it->next();
                    ++stage_cnt;
                    if (!it->has_next()) {
                        key0 = k;
                    }
                }
                key0.push_back('\x00');
            } while (it->more());
            ASSERT_EQ(stage_cnt, 1);
        }

        // get internal stage
        meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                &get_stage_req, &res2, nullptr);
        ASSERT_EQ(res2.status().code(), MetaServiceCode::STAGE_NOT_FOUND);

        // drop a non-exist internal stage
        drop_stage_request.set_mysql_user_id("root_id2");
        meta_service->drop_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                 &drop_stage_request, &drop_stage_response, nullptr);
        ASSERT_EQ(drop_stage_response.status().code(), MetaServiceCode::STAGE_NOT_FOUND);
    }

    // test create and get external stage
    {
        // get an external stage with name
        GetStageRequest get_stage_req;
        get_stage_req.set_cloud_unique_id(cloud_unique_id);
        get_stage_req.set_type(StagePB::EXTERNAL);
        get_stage_req.set_stage_name("ex_name_1");

        {
            GetStageResponse res;
            meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &get_stage_req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::STAGE_NOT_FOUND);
        }

        // create 4 stages
        for (auto i = 0; i < 4; ++i) {
            StagePB stage;
            stage.set_type(StagePB::EXTERNAL);
            stage.set_stage_id("ex_id_" + std::to_string(i));
            stage.set_name("ex_name_" + std::to_string(i));
            if (i == 2) {
                stage.set_access_type(StagePB::BUCKET_ACL);
            } else if (i == 3) {
                stage.set_access_type(StagePB::IAM);
            }
            stage.mutable_obj_info()->CopyFrom(obj);

            CreateStageRequest create_stage_req;
            create_stage_req.set_cloud_unique_id(cloud_unique_id);
            create_stage_req.mutable_stage()->CopyFrom(stage);

            CreateStageResponse create_stage_res;
            meta_service->create_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                       &create_stage_req, &create_stage_res, nullptr);
            ASSERT_EQ(create_stage_res.status().code(), MetaServiceCode::OK);
        }

        // stages number bigger than config
        {
            config::max_num_stages = 4;
            StagePB stage;
            stage.set_type(StagePB::INTERNAL);
            stage.add_mysql_user_name("root1");
            stage.add_mysql_user_id("root_id1");
            stage.set_stage_id("internal_stage_id1");
            CreateStageRequest create_stage_req;
            create_stage_req.set_cloud_unique_id(cloud_unique_id);
            create_stage_req.mutable_stage()->CopyFrom(stage);

            CreateStageResponse create_stage_res;
            meta_service->create_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                       &create_stage_req, &create_stage_res, nullptr);
            ASSERT_EQ(create_stage_res.status().code(), MetaServiceCode::UNDEFINED_ERR);
        }

        // get an external stage with name
        {
            GetStageResponse res;
            meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &get_stage_req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(1, res.stage().size());
            ASSERT_EQ("ex_id_1", res.stage().at(0).stage_id());
        }

        // get an external stage with name, type StagePB::BUCKET_ACL
        {
            get_stage_req.set_stage_name("ex_name_2");
            GetStageResponse res;
            meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &get_stage_req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(1, res.stage().size());
            ASSERT_EQ("ex_id_2", res.stage().at(0).stage_id());
        }

        // get an external stage with name, type StagePB::IAM
        {
            GetStageResponse res;
            get_stage_req.set_stage_name("ex_name_3");
            meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &get_stage_req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(1, res.stage().size());
            ASSERT_EQ("ex_id_3", res.stage().at(0).stage_id());

            GetStageResponse res1;
            std::unique_ptr<Transaction> txn;
            TxnErrorCode err = meta_service->txn_kv()->create_txn(&txn);
            ASSERT_EQ(err, TxnErrorCode::TXN_OK);

            RamUserPB iam_user;
            txn->put(system_meta_service_arn_info_key(), iam_user.SerializeAsString());
            err = txn->commit();
            ASSERT_EQ(err, TxnErrorCode::TXN_OK);
            LOG_INFO("err=", err);
            meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &get_stage_req, &res1, nullptr);
            ASSERT_EQ(res1.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(1, res1.stage().size());
            ASSERT_EQ("ex_id_3", res1.stage().at(0).stage_id());
        }

        GetStageRequest req;
        req.set_cloud_unique_id(cloud_unique_id);
        req.set_type(StagePB::EXTERNAL);
        // get all stages
        {
            GetStageResponse res;
            meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(4, res.stage().size());
            ASSERT_EQ("ex_id_0", res.stage().at(0).stage_id());
            ASSERT_EQ("ex_id_1", res.stage().at(1).stage_id());
        }

        // drop one stage
        {
            DropStageRequest drop_stage_req;
            drop_stage_req.set_cloud_unique_id(cloud_unique_id);
            drop_stage_req.set_type(StagePB::EXTERNAL);
            drop_stage_req.set_stage_name("tmp");
            DropStageResponse res;
            meta_service->drop_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &drop_stage_req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::STAGE_NOT_FOUND);

            drop_stage_req.set_stage_name("ex_name_1");
            meta_service->drop_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &drop_stage_req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

            // get all stage
            GetStageResponse get_stage_res;
            meta_service->get_stage(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &get_stage_res, nullptr);
            ASSERT_EQ(get_stage_res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(3, get_stage_res.stage().size());
            ASSERT_EQ("ex_name_0", get_stage_res.stage().at(0).name());
        }
    }
    sp->clear_all_call_backs();
    sp->clear_trace();
    sp->disable_processing();
}

TEST(MetaServiceTest, GetIamTest) {
    auto meta_service = get_meta_service();
    brpc::Controller cntl;
    auto cloud_unique_id = "test_cloud_unique_id";
    std::string instance_id = "get_iam_test_instance_id";
    [[maybe_unused]] auto sp = SyncPoint::get_instance();
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
    };
    sp->set_call_back("get_instance_id", [&](auto&& args) {
        auto* ret = try_any_cast_ret<std::string>(args);
        ret->first = instance_id;
        ret->second = true;
    });
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "test";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });
    sp->set_call_back("decrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* key = try_any_cast<std::string*>(args[0]);
        *key = "test";
        auto* ret = try_any_cast<int*>(args[1]);
        *ret = 0;
    });
    sp->enable_processing();

    config::arn_id = "iam_arn";
    config::arn_ak = "iam_ak";
    config::arn_sk = "iam_sk";

    // create instance
    {
        ObjectStoreInfoPB obj;
        obj.set_ak("123");
        obj.set_sk("321");
        obj.set_bucket("456");
        obj.set_prefix("654");
        obj.set_endpoint("789");
        obj.set_region("987");
        obj.set_external_endpoint("888");
        obj.set_provider(ObjectStoreInfoPB::BOS);

        RamUserPB ram_user;
        ram_user.set_user_id("test_user_id");
        ram_user.set_ak("test_ak");
        ram_user.set_sk("test_sk");

        CreateInstanceRequest req;
        req.set_instance_id(instance_id);
        req.set_user_id("test_user");
        req.set_name("test_name");
        req.mutable_ram_user()->CopyFrom(ram_user);
        req.mutable_obj_info()->CopyFrom(obj);

        CreateInstanceResponse res;
        meta_service->create_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                      &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    GetIamRequest request;
    request.set_cloud_unique_id(cloud_unique_id);
    GetIamResponse response;
    meta_service->get_iam(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &request,
                          &response, nullptr);
    ASSERT_EQ(response.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(response.ram_user().user_id(), "test_user_id");
    ASSERT_EQ(response.ram_user().ak(), "test_ak");
    ASSERT_EQ(response.ram_user().sk(), "test_sk");
    ASSERT_TRUE(response.ram_user().external_id().empty());

    ASSERT_EQ(response.iam_user().user_id(), "iam_arn");
    ASSERT_EQ(response.iam_user().external_id(), instance_id);
    ASSERT_EQ(response.iam_user().ak(), "iam_ak");
    ASSERT_EQ(response.iam_user().sk(), "iam_sk");
    sp->clear_all_call_backs();
    sp->clear_trace();
    sp->disable_processing();
}

TEST(MetaServiceTest, AlterRamTest) {
    auto meta_service = get_meta_service();
    brpc::Controller cntl;
    auto cloud_unique_id = "test_cloud_unique_id";
    std::string instance_id = "alter_iam_test_instance_id";
    [[maybe_unused]] auto sp = SyncPoint::get_instance();
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
    };
    sp->set_call_back("get_instance_id", [&](auto&& args) {
        auto* ret = try_any_cast_ret<std::string>(args);
        ret->first = instance_id;
        ret->second = true;
    });
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "test";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });
    sp->set_call_back("decrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* key = try_any_cast<std::string*>(args[0]);
        *key = "test";
        auto* ret = try_any_cast<int*>(args[1]);
        *ret = 0;
    });
    sp->enable_processing();

    config::arn_id = "iam_arn";
    config::arn_ak = "iam_ak";
    config::arn_sk = "iam_sk";

    ObjectStoreInfoPB obj;
    obj.set_ak("123");
    obj.set_sk("321");
    obj.set_bucket("456");
    obj.set_prefix("654");
    obj.set_endpoint("789");
    obj.set_region("987");
    obj.set_external_endpoint("888");
    obj.set_provider(ObjectStoreInfoPB::BOS);

    // create instance without ram user
    CreateInstanceRequest create_instance_req;
    create_instance_req.set_instance_id(instance_id);
    create_instance_req.set_user_id("test_user");
    create_instance_req.set_name("test_name");
    create_instance_req.mutable_obj_info()->CopyFrom(obj);
    CreateInstanceResponse create_instance_res;
    meta_service->create_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                  &create_instance_req, &create_instance_res, nullptr);
    ASSERT_EQ(create_instance_res.status().code(), MetaServiceCode::OK);

    // get iam and ram user
    GetIamRequest request;
    request.set_cloud_unique_id(cloud_unique_id);
    GetIamResponse response;
    meta_service->get_iam(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &request,
                          &response, nullptr);
    ASSERT_EQ(response.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(response.has_ram_user(), false);
    ASSERT_EQ(response.iam_user().user_id(), "iam_arn");
    ASSERT_EQ(response.iam_user().ak(), "iam_ak");
    ASSERT_EQ(response.iam_user().sk(), "iam_sk");

    // alter ram user
    RamUserPB ram_user;
    ram_user.set_user_id("test_user_id");
    ram_user.set_ak("test_ak");
    ram_user.set_sk("test_sk");
    AlterRamUserRequest alter_ram_user_request;
    alter_ram_user_request.set_instance_id(instance_id);
    alter_ram_user_request.mutable_ram_user()->CopyFrom(ram_user);
    AlterRamUserResponse alter_ram_user_response;
    meta_service->alter_ram_user(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                 &alter_ram_user_request, &alter_ram_user_response, nullptr);

    // get iam and ram user
    meta_service->get_iam(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &request,
                          &response, nullptr);
    ASSERT_EQ(response.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(response.has_ram_user(), true);
    ASSERT_EQ(response.ram_user().user_id(), "test_user_id");
    ASSERT_EQ(response.ram_user().ak(), "test_ak");
    ASSERT_EQ(response.ram_user().sk(), "test_sk");
    sp->clear_all_call_backs();
    sp->clear_trace();
    sp->disable_processing();
}

std::string to_raw_string(std::string_view v) {
    std::string ret;
    ret.reserve(v.size() / 1.5);
    while (!v.empty()) {
        if (v[0] == '\\') {
            if (v[1] == 'x') {
                ret.push_back(unhex(std::string_view {v.data() + 2, 2})[0]);
                v.remove_prefix(4);
            } else if (v[1] == '\\') {
                ret.push_back('\\');
                v.remove_prefix(2);
            } else {
                std::abort();
            }
            continue;
        }
        ret.push_back(v[0]);
        v.remove_prefix(1);
    }
    return ret;
}

TEST(MetaServiceTest, DecodeTest) {
    // 504
    std::string v1 =
            R"(\x08\x00\x10\xa0[\x18\xb3[ \xde\xc5\xa4\x8e\xbd\xf0\x97\xc62(\xf4\x96\xe6\xb0\x070\x018\x02@\x02H\x0bX\x05`\xa0\x07h\xa0\x07p\xa0\x01\x88\x01\x00\xa0\x01\x86\x8b\x9a\x9b\x06\xaa\x01\x16\x08\xe6\x9e\x91\xa3\xfb\xbe\xf5\xf0\xc4\x01\x10\xfe\x8b\x90\xa7\xb5\xec\xd5\xc8\xbf\x01\xb0\x01\x01\xba\x0100200000000000071fb4aabb58c570cbcadb10857d3131b97\xc2\x01\x011\xc8\x01\x84\x8b\x9a\x9b\x06\xd0\x01\x85\x8b\x9a\x9b\x06\xda\x01\x04\x0a\x00\x12\x00\xe2\x01\xcd\x02\x08\x02\x121\x08\x00\x12\x06datek1\x1a\x04DATE \x01*\x04NONE0\x01:\x0a2022-01-01@\x00H\x00P\x03X\x03\x80\x01\x01\x12>\x08\x01\x12\x06datek2\x1a\x08DATETIME \x01*\x04NONE0\x01:\x132022-01-01 11:11:11@\x00H\x00P\x08X\x08\x80\x01\x01\x123\x08\x04\x12\x06datev3\x1a\x06DATEV2 \x01*\x04NONE0\x01:\x0a2022-01-01@\x00H\x00P\x04X\x04\x80\x01\x01\x120\x08\x02\x12\x06datev1\x1a\x04DATE \x00*\x03MAX0\x01:\x0a2022-01-01@\x00H\x00P\x03X\x03\x80\x01\x01\x12=\x08\x03\x12\x06datev2\x1a\x08DATETIME \x00*\x03MAX0\x01:\x132022-01-01 11:11:11@\x00H\x00P\x08X\x08\x80\x01\x01\x18\x03 \x80\x08(\x021\x00\x00\x00\x00\x00\x00\x00\x008\x00@\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01H\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01P\x00X\x02`\x05h\x00p\x00\xe8\x01\x85\xae\x9f\x9b\x06\x98\x03\x02)";
    std::string val1 = to_raw_string(v1);
    std::cout << "val1 size " << val1.size() << std::endl;

    // 525
    std::string v2 =
            R"(\x08\x00\x10\xa0[\x18\xb3[ \x80\xb0\x85\xe3\xda\xcc\x8c\x0f(\xf4\x96\xe6\xb0\x070\x018\x01@\x0cH\x0cX\x00`\x00h\x00p\x00\x82\x01\x1e\x08\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x12\x11datev3=2022-01-01\x88\x01\x01\x92\x01\x04\x08\x00\x10\x00\xa0\x01\x87\x8b\x9a\x9b\x06\xaa\x01\x16\x08\xe6\x9e\x91\xa3\xfb\xbe\xf5\xf0\xc4\x01\x10\xfe\x8b\x90\xa7\xb5\xec\xd5\xc8\xbf\x01\xb0\x01\x00\xba\x0100200000000000072fb4aabb58c570cbcadb10857d3131b97\xc8\x01\x87\x8b\x9a\x9b\x06\xd0\x01\x87\x8b\x9a\x9b\x06\xe2\x01\xcd\x02\x08\x02\x121\x08\x00\x12\x06datek1\x1a\x04DATE \x01*\x04NONE0\x01:\x0a2022-01-01@\x00H\x00P\x03X\x03\x80\x01\x01\x12>\x08\x01\x12\x06datek2\x1a\x08DATETIME \x01*\x04NONE0\x01:\x132022-01-01 11:11:11@\x00H\x00P\x08X\x08\x80\x01\x01\x123\x08\x04\x12\x06datev3\x1a\x06DATEV2 \x01*\x04NONE0\x01:\x0a2022-01-01@\x00H\x00P\x04X\x04\x80\x01\x01\x120\x08\x02\x12\x06datev1\x1a\x04DATE \x00*\x03MAX0\x01:\x0a2022-01-01@\x00H\x00P\x03X\x03\x80\x01\x01\x12=\x08\x03\x12\x06datev2\x1a\x08DATETIME \x00*\x03MAX0\x01:\x132022-01-01 11:11:11@\x00H\x00P\x08X\x08\x80\x01\x01\x18\x03 \x80\x08(\x021\x00\x00\x00\x00\x00\x00\x00\x008\x00@\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01H\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01P\x00X\x02`\x05h\x00p\x00\xe8\x01\x00\x98\x03\x02)";
    std::string val2 = to_raw_string(v2);
    std::cout << "val2 size " << val2.size() << std::endl;

    [[maybe_unused]] std::string key1(
            "\x01\x10meta\x00\x01\x10selectdb-cloud-"
            "dev\x00\x01\x10rowset\x00\x01\x12\x00\x00\x00\x00\x00\x00-"
            "\xb3\x12\x00\x00\x00\x00\x00\x00\x00\x0b",
            56);
    [[maybe_unused]] std::string key2(
            "\x01\x10meta\x00\x01\x10selectdb-cloud-"
            "dev\x00\x01\x10rowset\x00\x01\x12\x00\x00\x00\x00\x00\x00-"
            "\xb3\x12\x00\x00\x00\x00\x00\x00\x00\x0c",
            56);
    std::cout << "key1 " << key1.size() << " " << hex(key1) << std::endl;
    std::cout << "key2 " << key2.size() << " " << hex(key2) << std::endl;

    doris::RowsetMetaCloudPB rowset1;
    doris::RowsetMetaCloudPB rowset2;

    rowset1.ParseFromString(val1);
    rowset2.ParseFromString(val2);
    std::cout << "rowset1=" << proto_to_json(rowset1) << std::endl;
    std::cout << "rowset2=" << proto_to_json(rowset2) << std::endl;
}

void get_tablet_stats(MetaServiceProxy* meta_service, int64_t table_id, int64_t index_id,
                      int64_t partition_id, int64_t tablet_id, GetTabletStatsResponse& res) {
    brpc::Controller cntl;
    GetTabletStatsRequest req;
    auto idx = req.add_tablet_idx();
    idx->set_table_id(table_id);
    idx->set_index_id(index_id);
    idx->set_partition_id(partition_id);
    idx->set_tablet_id(tablet_id);
    meta_service->get_tablet_stats(&cntl, &req, &res, nullptr);
}

TEST(MetaServiceTest, UpdateTablet) {
    auto meta_service = get_meta_service();
    std::string cloud_unique_id = "test_cloud_unique_id";
    constexpr auto table_id = 11231, index_id = 11232, partition_id = 11233, tablet_id1 = 11234,
                   tablet_id2 = 21234;
    ASSERT_NO_FATAL_FAILURE(
            create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id1));
    ASSERT_NO_FATAL_FAILURE(
            create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id2));
    auto get_and_check_tablet_meta = [&](int tablet_id, int64_t ttl_seconds, bool in_memory,
                                         bool is_persistent) {
        brpc::Controller cntl;
        GetTabletRequest req;
        req.set_cloud_unique_id(cloud_unique_id);
        req.set_tablet_id(tablet_id);
        GetTabletResponse resp;
        meta_service->get_tablet(&cntl, &req, &resp, nullptr);
        ASSERT_EQ(resp.status().code(), MetaServiceCode::OK) << tablet_id;
        EXPECT_EQ(resp.tablet_meta().ttl_seconds(), ttl_seconds);
        EXPECT_EQ(resp.tablet_meta().is_in_memory(), in_memory);
        EXPECT_EQ(resp.tablet_meta().is_persistent(), is_persistent);
    };
    get_and_check_tablet_meta(tablet_id1, 0, false, false);
    get_and_check_tablet_meta(tablet_id2, 0, false, false);
    {
        brpc::Controller cntl;
        UpdateTabletRequest req;
        UpdateTabletResponse resp;
        req.set_cloud_unique_id(cloud_unique_id);
        TabletMetaInfoPB* tablet_meta_info = req.add_tablet_meta_infos();
        tablet_meta_info->set_tablet_id(tablet_id1);
        tablet_meta_info->set_ttl_seconds(300);
        tablet_meta_info = req.add_tablet_meta_infos();
        tablet_meta_info->set_tablet_id(tablet_id2);
        tablet_meta_info->set_ttl_seconds(3000);
        meta_service->update_tablet(&cntl, &req, &resp, nullptr);
        ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
    }
    get_and_check_tablet_meta(tablet_id1, 300, false, false);
    get_and_check_tablet_meta(tablet_id2, 3000, false, false);
    {
        brpc::Controller cntl;
        UpdateTabletRequest req;
        UpdateTabletResponse resp;
        req.set_cloud_unique_id(cloud_unique_id);
        TabletMetaInfoPB* tablet_meta_info = req.add_tablet_meta_infos();
        tablet_meta_info->set_tablet_id(tablet_id1);
        tablet_meta_info->set_is_in_memory(true);
        meta_service->update_tablet(&cntl, &req, &resp, nullptr);
        ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
    }
    {
        brpc::Controller cntl;
        UpdateTabletRequest req;
        UpdateTabletResponse resp;
        req.set_cloud_unique_id(cloud_unique_id);
        TabletMetaInfoPB* tablet_meta_info = req.add_tablet_meta_infos();
        tablet_meta_info->set_tablet_id(tablet_id1);
        tablet_meta_info->set_is_persistent(true);
        meta_service->update_tablet(&cntl, &req, &resp, nullptr);
        ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
    }
    get_and_check_tablet_meta(tablet_id1, 300, true, true);
}

TEST(MetaServiceTest, GetTabletStatsTest) {
    auto meta_service = get_meta_service();

    constexpr auto table_id = 10001, index_id = 10002, partition_id = 10003, tablet_id = 10004;
    ASSERT_NO_FATAL_FAILURE(
            create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id));
    GetTabletStatsResponse res;
    get_tablet_stats(meta_service.get(), table_id, index_id, partition_id, tablet_id, res);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(res.tablet_stats_size(), 1);
    EXPECT_EQ(res.tablet_stats(0).data_size(), 0);
    EXPECT_EQ(res.tablet_stats(0).num_rows(), 0);
    EXPECT_EQ(res.tablet_stats(0).num_rowsets(), 1);
    EXPECT_EQ(res.tablet_stats(0).num_segments(), 0);
    EXPECT_EQ(res.tablet_stats(0).index_size(), 0);
    EXPECT_EQ(res.tablet_stats(0).segment_size(), 0);
    // Insert rowset
    config::split_tablet_stats = false;
    ASSERT_NO_FATAL_FAILURE(
            insert_rowset(meta_service.get(), 10000, "label1", table_id, partition_id, tablet_id));
    ASSERT_NO_FATAL_FAILURE(
            insert_rowset(meta_service.get(), 10000, "label2", table_id, partition_id, tablet_id));
    config::split_tablet_stats = true;
    ASSERT_NO_FATAL_FAILURE(
            insert_rowset(meta_service.get(), 10000, "label3", table_id, partition_id, tablet_id));
    ASSERT_NO_FATAL_FAILURE(
            insert_rowset(meta_service.get(), 10000, "label4", table_id, partition_id, tablet_id));
    // Check tablet stats kv
    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    std::string data_size_key, data_size_val;
    stats_tablet_data_size_key({mock_instance, table_id, index_id, partition_id, tablet_id},
                               &data_size_key);
    ASSERT_EQ(txn->get(data_size_key, &data_size_val), TxnErrorCode::TXN_OK);
    EXPECT_EQ(*(int64_t*)data_size_val.data(), 22000);
    std::string index_size_key, index_size_val;
    stats_tablet_index_size_key({mock_instance, table_id, index_id, partition_id, tablet_id},
                                &index_size_key);
    ASSERT_EQ(txn->get(index_size_key, &index_size_val), TxnErrorCode::TXN_OK);
    EXPECT_EQ(*(int64_t*)index_size_val.data(), 2000);
    std::string segment_size_key, segment_size_val;
    stats_tablet_segment_size_key({mock_instance, table_id, index_id, partition_id, tablet_id},
                                  &segment_size_key);
    ASSERT_EQ(txn->get(segment_size_key, &segment_size_val), TxnErrorCode::TXN_OK);
    EXPECT_EQ(*(int64_t*)segment_size_val.data(), 20000);
    std::string num_rows_key, num_rows_val;
    stats_tablet_num_rows_key({mock_instance, table_id, index_id, partition_id, tablet_id},
                              &num_rows_key);
    ASSERT_EQ(txn->get(num_rows_key, &num_rows_val), TxnErrorCode::TXN_OK);
    EXPECT_EQ(*(int64_t*)num_rows_val.data(), 200);
    std::string num_rowsets_key, num_rowsets_val;
    stats_tablet_num_rowsets_key({mock_instance, table_id, index_id, partition_id, tablet_id},
                                 &num_rowsets_key);
    ASSERT_EQ(txn->get(num_rowsets_key, &num_rowsets_val), TxnErrorCode::TXN_OK);
    EXPECT_EQ(*(int64_t*)num_rowsets_val.data(), 2);
    std::string num_segs_key, num_segs_val;
    stats_tablet_num_segs_key({mock_instance, table_id, index_id, partition_id, tablet_id},
                              &num_segs_key);
    ASSERT_EQ(txn->get(num_segs_key, &num_segs_val), TxnErrorCode::TXN_OK);
    EXPECT_EQ(*(int64_t*)num_segs_val.data(), 2);
    // Get tablet stats
    res.Clear();
    get_tablet_stats(meta_service.get(), table_id, index_id, partition_id, tablet_id, res);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(res.tablet_stats_size(), 1);
    EXPECT_EQ(res.tablet_stats(0).data_size(), 44000);
    EXPECT_EQ(res.tablet_stats(0).num_rows(), 400);
    EXPECT_EQ(res.tablet_stats(0).num_rowsets(), 5);
    EXPECT_EQ(res.tablet_stats(0).num_segments(), 4);
    EXPECT_EQ(res.tablet_stats(0).index_size(), 4000);
    EXPECT_EQ(res.tablet_stats(0).segment_size(), 40000);
}

void remove_delete_bitmap_lock(MetaServiceProxy* meta_service, int64_t table_id) {
    std::string lock_key = meta_delete_bitmap_update_lock_key({"test_instance", table_id, -1});
    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->remove(lock_key);
    std::string tablet_job_key_begin = mow_tablet_job_key({"test_instance", table_id, 0});
    std::string tablet_job_key_end = mow_tablet_job_key({"test_instance", table_id, INT64_MAX});
    txn->remove(tablet_job_key_begin, tablet_job_key_end);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
}

void testGetDeleteBitmapUpdateLock(int lock_version, int job_lock_id) {
    config::delete_bitmap_lock_v2_white_list = lock_version == 1 ? "" : "*";
    auto meta_service = get_meta_service();
    [[maybe_unused]] auto sp = SyncPoint::get_instance();
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
    };
    remove_delete_bitmap_lock(meta_service.get(), 1);
    remove_delete_bitmap_lock(meta_service.get(), 2);
    int64_t table_id = 9;

    // case 1: lock key does not exist, get and remove load lock
    brpc::Controller cntl;
    GetDeleteBitmapUpdateLockRequest req;
    GetDeleteBitmapUpdateLockResponse res;
    req.set_cloud_unique_id("test_cloud_unique_id");
    req.set_table_id(table_id);
    req.add_partition_ids(123);
    req.set_expiration(5);
    req.set_lock_id(888);
    req.set_initiator(-1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    RemoveDeleteBitmapUpdateLockRequest remove_req;
    RemoveDeleteBitmapUpdateLockResponse remove_res;
    remove_req.set_cloud_unique_id("test_cloud_unique_id");
    remove_req.set_table_id(table_id);
    remove_req.set_lock_id(888);
    remove_req.set_initiator(-1);
    meta_service->remove_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &remove_req, &remove_res,
            nullptr);
    ASSERT_EQ(remove_res.status().code(), MetaServiceCode::OK);

    // case 2: lock key does not exist, get and remove compaction lock
    req.add_partition_ids(123);
    req.set_expiration(600);
    req.set_lock_id(job_lock_id);
    req.set_initiator(100);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    remove_req.set_tablet_id(2);
    remove_req.set_lock_id(job_lock_id);
    remove_req.set_initiator(100);
    meta_service->remove_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &remove_req, &remove_res,
            nullptr);
    ASSERT_EQ(remove_res.status().code(), MetaServiceCode::OK);

    // case 3: lock key owned by load1, load2 get lock
    req.add_partition_ids(123);
    req.set_expiration(600);
    req.set_lock_id(888);
    req.set_initiator(-1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    req.set_lock_id(889);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::LOCK_CONFLICT);

    // case 4: lock key owned by load1, compaction1 get lock
    req.set_lock_id(job_lock_id);
    req.set_initiator(100);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::LOCK_CONFLICT);

    remove_req.set_tablet_id(2);
    remove_req.set_lock_id(888);
    remove_req.set_initiator(-1);
    meta_service->remove_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &remove_req, &remove_res,
            nullptr);
    ASSERT_EQ(remove_res.status().code(), MetaServiceCode::OK);

    // case 5: lock key owned by load1 but expired, load2 get lock
    req.add_partition_ids(123);
    req.set_expiration(1);
    req.set_lock_id(888);
    req.set_initiator(-1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    sleep(2);
    req.set_lock_id(889);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    remove_req.set_lock_id(889);
    remove_req.set_initiator(-1);
    meta_service->remove_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &remove_req, &remove_res,
            nullptr);
    ASSERT_EQ(remove_res.status().code(), MetaServiceCode::OK);

    // case 6: lock key owned by load1 but expired, compaction1 get lock
    req.add_partition_ids(123);
    req.set_expiration(1);
    req.set_lock_id(888);
    req.set_initiator(-1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    sleep(2);
    req.set_lock_id(job_lock_id);
    req.set_initiator(888);
    req.set_expiration(1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    remove_req.set_lock_id(job_lock_id);
    remove_req.set_initiator(888);
    meta_service->remove_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &remove_req, &remove_res,
            nullptr);
    ASSERT_EQ(remove_res.status().code(), MetaServiceCode::OK);

    // case 7: lock key owned by compaction, new compaction get lock
    req.set_lock_id(job_lock_id);
    req.set_initiator(100);
    req.set_expiration(100);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    req.set_lock_id(job_lock_id);
    req.set_initiator(101);
    req.set_expiration(1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    // new compaction get lock again
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    // case 8: lock key owned by compaction, load1 get lock
    req.set_lock_id(888);
    req.set_initiator(-1);
    req.set_expiration(60);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::LOCK_CONFLICT);

    remove_req.set_lock_id(job_lock_id);
    remove_req.set_initiator(100);
    meta_service->remove_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &remove_req, &remove_res,
            nullptr);
    ASSERT_EQ(remove_res.status().code(), MetaServiceCode::OK);

    // case 9: lock key owned by compaction but all expired (101 900), load1 get lock
    req.set_table_id(table_id);
    req.set_lock_id(job_lock_id);
    req.set_initiator(900);
    req.set_expiration(1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    sleep(2);
    req.set_table_id(table_id);
    req.set_lock_id(888);
    req.set_initiator(-1);
    req.set_expiration(60);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    remove_req.set_lock_id(888);
    remove_req.set_initiator(-1);
    meta_service->remove_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &remove_req, &remove_res,
            nullptr);
    ASSERT_EQ(remove_res.status().code(), MetaServiceCode::OK);

    // case 10: lock key owned by compaction but all expired (101 900), schema change get lock
    req.set_table_id(table_id);
    req.set_lock_id(job_lock_id);
    req.set_initiator(900);
    req.set_expiration(1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    sleep(2);
    req.set_table_id(table_id);
    int other_job_lock_id = job_lock_id == COMPACTION_DELETE_BITMAP_LOCK_ID
                                    ? SCHEMA_CHANGE_DELETE_BITMAP_LOCK_ID
                                    : COMPACTION_DELETE_BITMAP_LOCK_ID;
    req.set_lock_id(other_job_lock_id);
    req.set_initiator(100);
    req.set_expiration(1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    remove_req.set_lock_id(other_job_lock_id);
    remove_req.set_initiator(100);
    meta_service->remove_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &remove_req, &remove_res,
            nullptr);
    ASSERT_EQ(remove_res.status().code(), MetaServiceCode::OK);

    // case 11: lock by schema change but expired, compaction get lock but txn commit conflict, do fast retry
    sp->set_call_back("get_delete_bitmap_update_lock:commit:conflict", [&](auto&& args) {
        auto* first_retry = try_any_cast<bool*>(args[0]);
        auto lock_id = (try_any_cast<const GetDeleteBitmapUpdateLockRequest*>(args[1]))->lock_id();
        if (*first_retry && is_job_delete_bitmap_lock_id(lock_id)) {
            *try_any_cast<TxnErrorCode*>(args[2]) = TxnErrorCode::TXN_CONFLICT;
        } else {
            *try_any_cast<TxnErrorCode*>(args[2]) = TxnErrorCode::TXN_OK;
        }
    });
    sp->enable_processing();
    req.set_lock_id(job_lock_id);
    req.set_initiator(100);
    req.set_expiration(10);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    remove_req.set_lock_id(job_lock_id);
    remove_req.set_initiator(100);
    meta_service->remove_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &remove_req, &remove_res,
            nullptr);
    ASSERT_EQ(remove_res.status().code(), MetaServiceCode::OK);

    // case 12: lock by load but expired, compaction get lock but txn commit conflict, do fast retry
    req.set_lock_id(300);
    req.set_initiator(-1);
    req.set_expiration(1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    sleep(2);
    req.set_lock_id(job_lock_id);
    req.set_initiator(100);
    req.set_expiration(10);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    remove_delete_bitmap_lock(meta_service.get(), table_id);

    // case 13: lock key does not exist, compaction get lock but txn commit conflict, do fast retry
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    sp->clear_all_call_backs();
    sp->clear_trace();
    sp->disable_processing();
    remove_delete_bitmap_lock(meta_service.get(), table_id);
}

TEST(MetaServiceTest, GetDeleteBitmapUpdateLock) {
    testGetDeleteBitmapUpdateLock(2, COMPACTION_DELETE_BITMAP_LOCK_ID);
    testGetDeleteBitmapUpdateLock(2, SCHEMA_CHANGE_DELETE_BITMAP_LOCK_ID);
    testGetDeleteBitmapUpdateLock(1, COMPACTION_DELETE_BITMAP_LOCK_ID);
    testGetDeleteBitmapUpdateLock(1, SCHEMA_CHANGE_DELETE_BITMAP_LOCK_ID);
}

TEST(MetaServiceTest, GetDeleteBitmapUpdateLockNoReadStats) {
    auto meta_service = get_meta_service();

    brpc::Controller cntl;
    GetDeleteBitmapUpdateLockRequest req;
    GetDeleteBitmapUpdateLockResponse res;
    req.set_cloud_unique_id("test_cloud_unique_id");
    req.set_table_id(111);
    req.add_partition_ids(123);
    req.set_expiration(5);
    req.set_lock_id(888);
    req.set_initiator(-1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    // same lock_id
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    // different lock_id
    req.set_lock_id(999);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::LOCK_CONFLICT);

    // lock expired
    req.set_cloud_unique_id("test_cloud_unique_id");
    req.set_table_id(222);
    req.set_expiration(0);
    req.set_lock_id(666);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    sleep(1);
    req.set_lock_id(667);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
}

TEST(MetaServiceTest, GetDeleteBitmapUpdateLockTabletStatsNormal) {
    auto meta_service = get_meta_service();

    bool enable_batch_get_mow_tablet_stats_and_meta_vals[] = {false, true};
    for (bool val : enable_batch_get_mow_tablet_stats_and_meta_vals) {
        config::enable_batch_get_mow_tablet_stats_and_meta = val;

        std::string instance_id = "test_get_delete_bitmap_update_lock_normal";
        [[maybe_unused]] auto* sp = SyncPoint::get_instance();
        DORIS_CLOUD_DEFER {
            SyncPoint::get_instance()->disable_processing();
            SyncPoint::get_instance()->clear_all_call_backs();
        };
        sp->set_call_back("get_instance_id", [&](auto&& args) {
            auto* ret = try_any_cast_ret<std::string>(args);
            ret->first = instance_id;
            ret->second = true;
        });
        sp->enable_processing();

        int64_t db_id = 1000;
        int64_t table_id = 2001;
        int64_t index_id = 3001;
        // [(partition_id, tablet_id)]
        std::vector<std::array<int64_t, 2>> tablet_idxes {
                {70001, 12345}, {80001, 3456}, {90001, 6789}};

        add_tablet_metas(meta_service.get(), instance_id, table_id, index_id, tablet_idxes);

        GetDeleteBitmapUpdateLockResponse res;
        get_delete_bitmap_update_lock(meta_service.get(), res, db_id, table_id, index_id,
                                      tablet_idxes, 5, 999999, -1, true);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

        ASSERT_EQ(res.base_compaction_cnts().size(), tablet_idxes.size());
        for (const auto& base_compaction_cnt : res.base_compaction_cnts()) {
            ASSERT_EQ(base_compaction_cnt, 10);
        }
        ASSERT_EQ(res.cumulative_compaction_cnts().size(), tablet_idxes.size());
        for (const auto& cumu_compaction_cnt : res.cumulative_compaction_cnts()) {
            ASSERT_EQ(cumu_compaction_cnt, 20);
        }
        ASSERT_EQ(res.cumulative_points().size(), tablet_idxes.size());
        for (const auto& cumulative_point : res.cumulative_points()) {
            ASSERT_EQ(cumulative_point, 30);
        }
    }
}

TEST(MetaServiceTest, GetDeleteBitmapUpdateLockTabletStatsLockExpired) {
    auto meta_service = get_meta_service();

    bool enable_batch_get_mow_tablet_stats_and_meta_vals[] = {false, true};
    for (bool val : enable_batch_get_mow_tablet_stats_and_meta_vals) {
        config::enable_batch_get_mow_tablet_stats_and_meta = val;
        // 2.1 abnormal path, lock has been expired and taken by another load/compaction during
        // the reading of tablet stats
        std::string instance_id = "test_get_delete_bitmap_update_lock_abnormal1";
        [[maybe_unused]] auto* sp = SyncPoint::get_instance();
        DORIS_CLOUD_DEFER {
            SyncPoint::get_instance()->disable_processing();
            SyncPoint::get_instance()->clear_all_call_backs();
        };
        sp->set_call_back("get_instance_id", [&](auto&& args) {
            auto* ret = try_any_cast_ret<std::string>(args);
            ret->first = instance_id;
            ret->second = true;
        });
        sp->set_call_back("check_delete_bitmap_lock.set_lock_info", [&](auto&& args) {
            auto* lock_info = try_any_cast<DeleteBitmapUpdateLockPB*>(args[0]);
            // simulate that lock_id has been modified by another load
            lock_info->set_lock_id(345);
            LOG(INFO) << "change lock_info.lock_id to 345, lock_info=" << lock_info->DebugString();
        });

        sp->enable_processing();

        int64_t db_id = 1000;
        int64_t table_id = 2001;
        int64_t index_id = 3001;
        // [(partition_id, tablet_id)]
        std::vector<std::array<int64_t, 2>> tablet_idxes {
                {70001, 12345}, {80001, 3456}, {90001, 6789}};

        add_tablet_metas(meta_service.get(), instance_id, table_id, index_id, tablet_idxes);

        GetDeleteBitmapUpdateLockResponse res;
        get_delete_bitmap_update_lock(meta_service.get(), res, db_id, table_id, index_id,
                                      tablet_idxes, 5, 999999, -1, true);
        EXPECT_EQ(res.status().code(), MetaServiceCode::LOCK_EXPIRED);
        EXPECT_EQ(res.base_compaction_cnts().size(), 3);
        EXPECT_EQ(res.cumulative_compaction_cnts().size(), 3);
        EXPECT_EQ(res.cumulative_points().size(), 3);
    }

    for (bool val : enable_batch_get_mow_tablet_stats_and_meta_vals) {
        config::enable_batch_get_mow_tablet_stats_and_meta = val;

        // 2.2 abnormal path, lock has been taken by another load/compaction and been released during
        // the reading of tablet stats
        std::string instance_id = "test_get_delete_bitmap_update_lock_abnormal2";
        [[maybe_unused]] auto* sp = SyncPoint::get_instance();
        DORIS_CLOUD_DEFER {
            SyncPoint::get_instance()->disable_processing();
            SyncPoint::get_instance()->clear_all_call_backs();
        };
        sp->set_call_back("get_instance_id", [&](auto&& args) {
            auto* ret = try_any_cast_ret<std::string>(args);
            ret->first = instance_id;
            ret->second = true;
        });
        sp->set_call_back("check_delete_bitmap_lock.inject_get_lock_key_err", [&](auto&& args) {
            auto* err = try_any_cast<TxnErrorCode*>(args[0]);
            // the lock has been taken by another load and been released,
            // so the delete bitmap update lock KV will be removed
            *err = TxnErrorCode::TXN_KEY_NOT_FOUND;
        });

        sp->enable_processing();

        int64_t db_id = 1000;
        int64_t table_id = 2001;
        int64_t index_id = 3001;
        // [(partition_id, tablet_id)]
        std::vector<std::array<int64_t, 2>> tablet_idxes {
                {70001, 12345}, {80001, 3456}, {90001, 6789}};

        add_tablet_metas(meta_service.get(), instance_id, table_id, index_id, tablet_idxes);

        GetDeleteBitmapUpdateLockResponse res;
        get_delete_bitmap_update_lock(meta_service.get(), res, db_id, table_id, index_id,
                                      tablet_idxes, 5, 999999, -1, true);
        ASSERT_EQ(res.status().code(), MetaServiceCode::LOCK_EXPIRED);
    }
}

TEST(MetaServiceTest, GetDeleteBitmapUpdateLockTabletStatsError) {
    auto meta_service = get_meta_service();

    bool enable_batch_get_mow_tablet_stats_and_meta_vals[] = {false, true};
    for (bool val : enable_batch_get_mow_tablet_stats_and_meta_vals) {
        config::enable_batch_get_mow_tablet_stats_and_meta = val;
        // 2.3 abnormal path, meeting error when reading tablets' stats
        std::string instance_id = "test_get_delete_bitmap_update_lock_abnormal3";
        [[maybe_unused]] auto* sp = SyncPoint::get_instance();
        DORIS_CLOUD_DEFER {
            SyncPoint::get_instance()->disable_processing();
            SyncPoint::get_instance()->clear_all_call_backs();
        };
        sp->set_call_back("get_instance_id", [&](auto&& args) {
            auto* ret = try_any_cast_ret<std::string>(args);
            ret->first = instance_id;
            ret->second = true;
        });

        TxnErrorCode injected_error_code {TxnErrorCode::TXN_KEY_NOT_FOUND};
        sp->set_call_back("get_delete_bitmap_update_lock.get_compaction_cnts_inject_error",
                          [&](auto&& args) {
                              auto* err = try_any_cast<TxnErrorCode*>(args[0]);
                              *err = injected_error_code;
                          });

        sp->enable_processing();

        int64_t db_id = 1000;
        int64_t table_id = 2001;
        int64_t index_id = 3001;
        // [(partition_id, tablet_id)]
        std::vector<std::array<int64_t, 2>> tablet_idxes {
                {70001, 12345}, {80001, 3456}, {90001, 6789}};

        add_tablet_metas(meta_service.get(), instance_id, table_id, index_id, tablet_idxes);

        GetDeleteBitmapUpdateLockResponse res;
        get_delete_bitmap_update_lock(meta_service.get(), res, db_id, table_id, index_id,
                                      tablet_idxes, 5, 999999, -1, true);
        ASSERT_EQ(res.status().code(), MetaServiceCode::KV_TXN_GET_ERR);
    }

    for (bool val : enable_batch_get_mow_tablet_stats_and_meta_vals) {
        config::enable_batch_get_mow_tablet_stats_and_meta = val;
        // 2.4 abnormal path, meeting TXN_TOO_OLD error when reading tablets' stats,
        // this should not fail if lock is not expired
        std::string instance_id = "test_get_delete_bitmap_update_lock_abnormal4";
        [[maybe_unused]] auto* sp = SyncPoint::get_instance();
        DORIS_CLOUD_DEFER {
            SyncPoint::get_instance()->disable_processing();
            SyncPoint::get_instance()->clear_all_call_backs();
        };
        sp->set_call_back("get_instance_id", [&](auto&& args) {
            auto* ret = try_any_cast_ret<std::string>(args);
            ret->first = instance_id;
            ret->second = true;
        });

        int counter = 0;
        sp->set_call_back("get_delete_bitmap_update_lock.get_compaction_cnts_inject_error",
                          [&](auto&& args) {
                              if (counter++ % 2 == 0) {
                                  auto* err = try_any_cast<TxnErrorCode*>(args[0]);
                                  *err = TxnErrorCode::TXN_TOO_OLD;
                              }
                          });

        sp->enable_processing();

        int64_t db_id = 1000;
        int64_t table_id = 2001;
        int64_t index_id = 3001;
        // [(partition_id, tablet_id)]
        std::vector<std::array<int64_t, 2>> tablet_idxes;
        for (int i = 0; i < 20; i++) {
            int64_t partition_id = 70000 + i;
            int64_t tablet_id = 80000 + i;
            tablet_idxes.push_back({partition_id, tablet_id});
        }

        add_tablet_metas(meta_service.get(), instance_id, table_id, index_id, tablet_idxes);

        GetDeleteBitmapUpdateLockResponse res;
        get_delete_bitmap_update_lock(meta_service.get(), res, db_id, table_id, index_id,
                                      tablet_idxes, 5, 999999, -1, true);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.base_compaction_cnts().size(), tablet_idxes.size());
        for (const auto& base_compaction_cnt : res.base_compaction_cnts()) {
            ASSERT_EQ(base_compaction_cnt, 10);
        }
        ASSERT_EQ(res.cumulative_compaction_cnts().size(), tablet_idxes.size());
        for (const auto& cumu_compaction_cnt : res.cumulative_compaction_cnts()) {
            ASSERT_EQ(cumu_compaction_cnt, 20);
        }
        ASSERT_EQ(res.cumulative_points().size(), tablet_idxes.size());
        for (const auto& cumulative_point : res.cumulative_points()) {
            ASSERT_EQ(cumulative_point, 30);
        }
    }

    for (bool val : enable_batch_get_mow_tablet_stats_and_meta_vals) {
        config::enable_batch_get_mow_tablet_stats_and_meta = val;
        // 2.5 abnormal path, meeting error when reading tablets' meta
        std::string instance_id = "test_get_delete_bitmap_update_lock_abnormal5";
        [[maybe_unused]] auto* sp = SyncPoint::get_instance();
        DORIS_CLOUD_DEFER {
            SyncPoint::get_instance()->disable_processing();
            SyncPoint::get_instance()->clear_all_call_backs();
        };
        sp->set_call_back("get_instance_id", [&](auto&& args) {
            auto* ret = try_any_cast_ret<std::string>(args);
            ret->first = instance_id;
            ret->second = true;
        });

        sp->enable_processing();

        int64_t db_id = 1000;
        int64_t table_id = 2001;
        int64_t index_id = 3001;
        // [(partition_id, tablet_id)]
        std::vector<std::array<int64_t, 2>> tablet_idxes {
                {70001, 12345}, {80001, 3456}, {90001, 6789}};

        add_tablet_metas(meta_service.get(), instance_id, table_id, index_id, tablet_idxes, true);

        GetDeleteBitmapUpdateLockResponse res;
        get_delete_bitmap_update_lock(meta_service.get(), res, db_id, table_id, index_id,
                                      tablet_idxes, 5, 999999, -1, true);
        ASSERT_EQ(res.status().code(), MetaServiceCode::TABLET_NOT_FOUND);
    }
}

static std::string generate_random_string(int length) {
    std::string char_set = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    std::random_device rd;
    std::mt19937 generator(rd());
    std::uniform_int_distribution<int> distribution(0, char_set.length() - 1);

    std::string randomString;
    for (int i = 0; i < length; ++i) {
        randomString += char_set[distribution(generator)];
    }
    return randomString;
}

TEST(MetaServiceTest, UpdateDeleteBitmapWithBigKeys) {
    auto meta_service = get_meta_service();
    // get delete bitmap update lock
    brpc::Controller cntl;
    GetDeleteBitmapUpdateLockRequest get_lock_req;
    GetDeleteBitmapUpdateLockResponse get_lock_res;
    get_lock_req.set_cloud_unique_id("test_cloud_unique_id");
    get_lock_req.set_table_id(1999);
    get_lock_req.add_partition_ids(123);
    get_lock_req.set_expiration(5);
    get_lock_req.set_lock_id(-1);
    get_lock_req.set_initiator(100);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &get_lock_req,
            &get_lock_res, nullptr);
    ASSERT_EQ(get_lock_res.status().code(), MetaServiceCode::OK);
    // v1 update delete bitmap
    UpdateDeleteBitmapRequest update_delete_bitmap_req;
    UpdateDeleteBitmapResponse update_delete_bitmap_res;
    update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
    update_delete_bitmap_req.set_table_id(1999);
    update_delete_bitmap_req.set_partition_id(123);
    update_delete_bitmap_req.set_lock_id(-1);
    update_delete_bitmap_req.set_initiator(100);
    update_delete_bitmap_req.set_tablet_id(333);
    std::string large_value = generate_random_string(300 * 1000 * 3);
    DeleteBitmapPB delete_bitmap_pb;
    auto num = 100000;
    for (int i = 0; i < num; i++) {
        update_delete_bitmap_req.add_rowset_ids("0200000003ea308a3647dbea83220ed4b8897f2288244a91");
        update_delete_bitmap_req.add_segment_ids(0);
        update_delete_bitmap_req.add_versions(i);
        update_delete_bitmap_req.add_segment_delete_bitmaps(i % 500 == 0 ? large_value : "1");

        delete_bitmap_pb.add_rowset_ids("0200000003ea308a3647dbea83220ed4b8897f2288244a91");
        delete_bitmap_pb.add_segment_ids(0);
        delete_bitmap_pb.add_versions(i);
        delete_bitmap_pb.add_segment_delete_bitmaps(i % 500 == 0 ? large_value : "1");
    }
    // v2 update delete bitmap
    DeleteBitmapStoragePB delete_bitmap_storage_pb;
    delete_bitmap_storage_pb.set_store_in_fdb(true);
    *(delete_bitmap_storage_pb.mutable_delete_bitmap()) = std::move(delete_bitmap_pb);
    *(update_delete_bitmap_req.add_delete_bitmap_storages()) = std::move(delete_bitmap_storage_pb);
    update_delete_bitmap_req.add_delta_rowset_ids(
            "0200000003ea308a3647dbea83220ed4b8897f2288244a91");

    meta_service->update_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                       &update_delete_bitmap_req, &update_delete_bitmap_res,
                                       nullptr);
    ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);

    // get delete bitmap v1
    GetDeleteBitmapRequest get_delete_bitmap_req;
    get_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
    get_delete_bitmap_req.set_tablet_id(333);
    get_delete_bitmap_req.add_rowset_ids("0200000003ea308a3647dbea83220ed4b8897f2288244a91");
    get_delete_bitmap_req.add_begin_versions(0);
    get_delete_bitmap_req.add_end_versions(num);
    {
        // get exceed max_get_delete_bitmap_byte limit
        auto max_get_delete_bitmap_byte = config::max_get_delete_bitmap_byte;
        config::max_get_delete_bitmap_byte = 1;
        GetDeleteBitmapResponse get_delete_bitmap_res;
        meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                        &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
        ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::KV_TXN_GET_ERR);
        config::max_get_delete_bitmap_byte = max_get_delete_bitmap_byte;
    }
    {
        GetDeleteBitmapResponse get_delete_bitmap_res;
        meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                        &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
        ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(get_delete_bitmap_res.rowset_ids_size(), num);
        ASSERT_EQ(get_delete_bitmap_res.versions_size(), num);
        ASSERT_EQ(get_delete_bitmap_res.segment_ids_size(), num);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), num);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(0), large_value);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(1), "1");
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(500), large_value);
    }

    // get delete bitmap v2
    {
        GetDeleteBitmapResponse get_delete_bitmap_res;
        get_delete_bitmap_req.set_store_version(2);
        meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                        &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
        ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(get_delete_bitmap_res.delete_bitmap_storages_size(), 1);
        auto& delete_bitmap_storage = get_delete_bitmap_res.delete_bitmap_storages(0);
        ASSERT_TRUE(delete_bitmap_storage.store_in_fdb());
        auto& delete_bitmap_pb = delete_bitmap_storage.delete_bitmap();
        ASSERT_EQ(delete_bitmap_pb.rowset_ids_size(), num);
        ASSERT_EQ(delete_bitmap_pb.versions_size(), num);
        ASSERT_EQ(delete_bitmap_pb.segment_ids_size(), num);
        ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps_size(), num);
        ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps(0), large_value);
        ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps(1), "1");
        ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps(500), large_value);
    }
}

static void set_partition_version(MetaServiceProxy* meta_service, std::string_view instance_id,
                                  int64_t db_id, int64_t table_id, int64_t partition_id,
                                  int64_t version, std::vector<int64_t> pending_txn_ids = {}) {
    std::string ver_key = partition_version_key({instance_id, db_id, table_id, partition_id});
    std::string ver_val;
    VersionPB version_pb;
    version_pb.set_version(version);
    if (!pending_txn_ids.empty()) {
        for (auto txn_id : pending_txn_ids) {
            version_pb.add_pending_txn_ids(txn_id);
        }
    }
    ASSERT_TRUE(version_pb.SerializeToString(&ver_val));
    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(ver_key, ver_val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
}

static void begin_txn_and_commit_rowset(MetaServiceProxy* meta_service, const std::string& label,
                                        int64_t db_id, int64_t table_id, int64_t partition_id,
                                        int64_t tablet_id, int64_t* txn_id) {
    begin_txn(meta_service, db_id, label, table_id, *txn_id);
    CreateRowsetResponse res;
    auto rowset = create_rowset(*txn_id, tablet_id, partition_id);
    prepare_rowset(meta_service, rowset, res);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    res.Clear();
    commit_rowset(meta_service, rowset, res);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
}

static void get_delete_bitmap_update_lock(MetaServiceProxy* meta_service, int64_t table_id,
                                          int64_t partition_id, int64_t lock_id,
                                          int64_t initiator) {
    brpc::Controller cntl;
    GetDeleteBitmapUpdateLockRequest get_lock_req;
    GetDeleteBitmapUpdateLockResponse get_lock_res;
    get_lock_req.set_cloud_unique_id("test_cloud_unique_id");
    get_lock_req.set_table_id(table_id);
    get_lock_req.add_partition_ids(partition_id);
    get_lock_req.set_expiration(5);
    get_lock_req.set_lock_id(lock_id);
    get_lock_req.set_initiator(initiator);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &get_lock_req,
            &get_lock_res, nullptr);
    ASSERT_EQ(get_lock_res.status().code(), MetaServiceCode::OK);
}

static void update_delete_bitmap(MetaServiceProxy* meta_service,
                                 UpdateDeleteBitmapRequest& update_delete_bitmap_req,
                                 UpdateDeleteBitmapResponse& update_delete_bitmap_res,
                                 int64_t table_id, int64_t partition_id, int64_t lock_id,
                                 int64_t initiator, int64_t tablet_id, int64_t txn_id,
                                 int64_t next_visible_version, std::string data = "1111") {
    brpc::Controller cntl;
    update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
    update_delete_bitmap_req.set_table_id(table_id);
    update_delete_bitmap_req.set_partition_id(partition_id);
    update_delete_bitmap_req.set_lock_id(lock_id);
    update_delete_bitmap_req.set_initiator(initiator);
    update_delete_bitmap_req.set_tablet_id(tablet_id);
    update_delete_bitmap_req.set_txn_id(txn_id);
    update_delete_bitmap_req.set_next_visible_version(next_visible_version);
    update_delete_bitmap_req.add_rowset_ids("123");
    update_delete_bitmap_req.add_segment_ids(0);
    update_delete_bitmap_req.add_versions(next_visible_version);
    update_delete_bitmap_req.add_segment_delete_bitmaps(data);
    meta_service->update_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                       &update_delete_bitmap_req, &update_delete_bitmap_res,
                                       nullptr);
}

TEST(MetaServiceTest, UpdateDeleteBitmapCheckPartitionVersion) {
    auto meta_service = get_meta_service();
    brpc::Controller cntl;

    extern std::string get_instance_id(const std::shared_ptr<ResourceManager>& rc_mgr,
                                       const std::string& cloud_unique_id);
    auto instance_id = get_instance_id(meta_service->resource_mgr(), "test_cloud_unique_id");

    {
        // 1. normal path
        // 1.1 has partition version and request version matches
        int64_t db_id = 999;
        int64_t table_id = 1001;
        int64_t index_id = 4001;
        int64_t t1p1 = 2001;
        int64_t tablet_id = 3001;
        int64_t initiator = -1;
        int64_t cur_max_version = 100;
        int64_t txn_id;
        ASSERT_NO_FATAL_FAILURE(create_tablet_with_db_id(meta_service.get(), db_id, table_id,
                                                         index_id, t1p1, tablet_id));
        begin_txn_and_commit_rowset(meta_service.get(), "label11", db_id, table_id, t1p1, tablet_id,
                                    &txn_id);
        int64_t lock_id = txn_id;

        get_delete_bitmap_update_lock(meta_service.get(), table_id, t1p1, lock_id, initiator);
        set_partition_version(meta_service.get(), instance_id, db_id, table_id, t1p1,
                              cur_max_version);

        UpdateDeleteBitmapRequest update_delete_bitmap_req;
        UpdateDeleteBitmapResponse update_delete_bitmap_res;
        update_delete_bitmap(meta_service.get(), update_delete_bitmap_req, update_delete_bitmap_res,
                             table_id, t1p1, lock_id, initiator, tablet_id, txn_id,
                             cur_max_version + 1);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);
    }

    {
        // 1. normal path
        // 1.2 does not have partition version KV and request version matches
        int64_t db_id = 999;
        int64_t table_id = 1002;
        int64_t index_id = 4001;
        int64_t t1p1 = 2001;
        int64_t tablet_id = 3001;
        int64_t initiator = -1;
        int64_t txn_id;
        ASSERT_NO_FATAL_FAILURE(create_tablet_with_db_id(meta_service.get(), db_id, table_id,
                                                         index_id, t1p1, tablet_id));
        begin_txn_and_commit_rowset(meta_service.get(), "label12", db_id, table_id, t1p1, tablet_id,
                                    &txn_id);
        int64_t lock_id = txn_id;

        get_delete_bitmap_update_lock(meta_service.get(), table_id, t1p1, lock_id, initiator);

        UpdateDeleteBitmapRequest update_delete_bitmap_req;
        UpdateDeleteBitmapResponse update_delete_bitmap_res;
        update_delete_bitmap(meta_service.get(), update_delete_bitmap_req, update_delete_bitmap_res,
                             table_id, t1p1, lock_id, initiator, tablet_id, txn_id, 2);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);
    }

    {
        // 1. normal path
        // 1.3 has partition version and pending txn, and request version matches
        int64_t db_id = 999;
        int64_t table_id = 1003;
        int64_t index_id = 4001;
        int64_t t1p1 = 2001;
        int64_t tablet_id = 3001;
        int64_t initiator = -1;
        int64_t cur_max_version = 120;
        int64_t txn_id;
        ASSERT_NO_FATAL_FAILURE(create_tablet_with_db_id(meta_service.get(), db_id, table_id,
                                                         index_id, t1p1, tablet_id));
        begin_txn_and_commit_rowset(meta_service.get(), "label13", db_id, table_id, t1p1, tablet_id,
                                    &txn_id);
        int64_t lock_id = txn_id;

        get_delete_bitmap_update_lock(meta_service.get(), table_id, t1p1, lock_id, initiator);
        set_partition_version(meta_service.get(), instance_id, db_id, table_id, t1p1,
                              cur_max_version, {12345});

        UpdateDeleteBitmapRequest update_delete_bitmap_req;
        UpdateDeleteBitmapResponse update_delete_bitmap_res;
        update_delete_bitmap(meta_service.get(), update_delete_bitmap_req, update_delete_bitmap_res,
                             table_id, t1p1, lock_id, initiator, tablet_id, txn_id,
                             cur_max_version + 2);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);
    }
}

TEST(MetaServiceTest, UpdateDeleteBitmapCheckPartitionVersionFail) {
    auto meta_service = get_meta_service();
    brpc::Controller cntl;

    extern std::string get_instance_id(const std::shared_ptr<ResourceManager>& rc_mgr,
                                       const std::string& cloud_unique_id);
    auto instance_id = get_instance_id(meta_service->resource_mgr(), "test_cloud_unique_id");

    {
        // 2. abnormal path
        // 2.1 has partition version but request version does not match
        int64_t db_id = 999;
        int64_t table_id = 2001;
        int64_t index_id = 4001;
        int64_t t1p1 = 2001;
        int64_t tablet_id = 3001;
        int64_t initiator = -1;
        int64_t cur_max_version = 100;
        int64_t txn_id;
        ASSERT_NO_FATAL_FAILURE(create_tablet_with_db_id(meta_service.get(), db_id, table_id,
                                                         index_id, t1p1, tablet_id));
        begin_txn_and_commit_rowset(meta_service.get(), "label21", db_id, table_id, t1p1, tablet_id,
                                    &txn_id);
        int64_t lock_id = txn_id;

        get_delete_bitmap_update_lock(meta_service.get(), table_id, t1p1, lock_id, initiator);
        set_partition_version(meta_service.get(), instance_id, db_id, table_id, t1p1,
                              cur_max_version);

        UpdateDeleteBitmapRequest update_delete_bitmap_req;
        UpdateDeleteBitmapResponse update_delete_bitmap_res;
        // wrong version
        update_delete_bitmap(meta_service.get(), update_delete_bitmap_req, update_delete_bitmap_res,
                             table_id, t1p1, lock_id, initiator, tablet_id, txn_id,
                             cur_max_version + 2);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::VERSION_NOT_MATCH);
    }

    {
        // 2. abnormal path
        // 2.2 does not have partition version KV and request version does not match
        int64_t db_id = 999;
        int64_t table_id = 2002;
        int64_t index_id = 4001;
        int64_t t1p1 = 2001;
        int64_t tablet_id = 3001;
        int64_t initiator = -1;
        int64_t txn_id;
        ASSERT_NO_FATAL_FAILURE(create_tablet_with_db_id(meta_service.get(), db_id, table_id,
                                                         index_id, t1p1, tablet_id));
        begin_txn_and_commit_rowset(meta_service.get(), "label22", db_id, table_id, t1p1, tablet_id,
                                    &txn_id);
        int64_t lock_id = txn_id;

        get_delete_bitmap_update_lock(meta_service.get(), table_id, t1p1, lock_id, initiator);

        UpdateDeleteBitmapRequest update_delete_bitmap_req;
        UpdateDeleteBitmapResponse update_delete_bitmap_res;
        // first load, wrong version
        update_delete_bitmap(meta_service.get(), update_delete_bitmap_req, update_delete_bitmap_res,
                             table_id, t1p1, lock_id, initiator, tablet_id, txn_id, 10);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::VERSION_NOT_MATCH);
    }

    {
        // 2. abnormal path
        // 2.3 has partition version and pending txn, and request version matches
        int64_t db_id = 999;
        int64_t table_id = 2003;
        int64_t index_id = 4001;
        int64_t t1p1 = 2001;
        int64_t tablet_id = 3001;
        int64_t initiator = -1;
        int64_t cur_max_version = 120;
        int64_t txn_id;
        ASSERT_NO_FATAL_FAILURE(create_tablet_with_db_id(meta_service.get(), db_id, table_id,
                                                         index_id, t1p1, tablet_id));
        begin_txn_and_commit_rowset(meta_service.get(), "label23", db_id, table_id, t1p1, tablet_id,
                                    &txn_id);
        int64_t lock_id = txn_id;

        get_delete_bitmap_update_lock(meta_service.get(), table_id, t1p1, lock_id, initiator);
        set_partition_version(meta_service.get(), instance_id, db_id, table_id, t1p1,
                              cur_max_version, {12345});

        UpdateDeleteBitmapRequest update_delete_bitmap_req;
        UpdateDeleteBitmapResponse update_delete_bitmap_res;
        // wrong version
        update_delete_bitmap(meta_service.get(), update_delete_bitmap_req, update_delete_bitmap_res,
                             table_id, t1p1, lock_id, initiator, tablet_id, txn_id,
                             cur_max_version + 1);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::VERSION_NOT_MATCH);
    }
}

TEST(MetaServiceTest, UpdateDeleteBitmapFailCase) {
    // simulate the situation described in https://github.com/apache/doris/pull/49710
    auto meta_service = get_meta_service();
    brpc::Controller cntl;
    extern std::string get_instance_id(const std::shared_ptr<ResourceManager>& rc_mgr,
                                       const std::string& cloud_unique_id);
    auto instance_id = get_instance_id(meta_service->resource_mgr(), "test_cloud_unique_id");

    int64_t db_id = 1999;
    int64_t table_id = 1001;
    int64_t index_id = 4001;
    int64_t t1p1 = 2001;
    int64_t tablet_id = 3001;
    int64_t initiator = -1;
    int64_t cur_max_version = 100;
    set_partition_version(meta_service.get(), instance_id, db_id, table_id, t1p1, cur_max_version);
    ASSERT_NO_FATAL_FAILURE(create_tablet_with_db_id(meta_service.get(), db_id, table_id, index_id,
                                                     t1p1, tablet_id));

    // txn1 begins
    int64_t txn_id1;
    begin_txn_and_commit_rowset(meta_service.get(), "label31", db_id, table_id, t1p1, tablet_id,
                                &txn_id1);
    int64_t txn1_version_to_publish = cur_max_version + 1;
    // txn1 gains the lock and try to publish with version 101
    int64_t lock_id = txn_id1;
    get_delete_bitmap_update_lock(meta_service.get(), table_id, t1p1, lock_id, initiator);

    // txn1 failed due to calculation timeout and removes the delete bitmap lock
    RemoveDeleteBitmapUpdateLockRequest remove_req;
    RemoveDeleteBitmapUpdateLockResponse remove_res;
    remove_req.set_cloud_unique_id("test_cloud_unique_id");
    remove_req.set_table_id(table_id);
    remove_req.set_lock_id(lock_id);
    remove_req.set_initiator(-1);
    meta_service->remove_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &remove_req, &remove_res,
            nullptr);
    ASSERT_EQ(remove_res.status().code(), MetaServiceCode::OK);

    // txn2 gains the lock and succeeds to publish with version 101
    int64_t txn_id2;
    begin_txn_and_commit_rowset(meta_service.get(), "label32", db_id, table_id, t1p1, tablet_id,
                                &txn_id2);
    lock_id = txn_id2;
    get_delete_bitmap_update_lock(meta_service.get(), table_id, t1p1, lock_id, initiator);

    int64_t txn2_version_to_publish = cur_max_version + 1;
    UpdateDeleteBitmapRequest update_delete_bitmap_req;
    UpdateDeleteBitmapResponse update_delete_bitmap_res;
    std::string data1 = "1234";
    update_delete_bitmap(meta_service.get(), update_delete_bitmap_req, update_delete_bitmap_res,
                         table_id, t1p1, lock_id, initiator, tablet_id, txn_id2,
                         txn2_version_to_publish, data1);

    CommitTxnRequest req;
    req.set_cloud_unique_id("test_cloud_unique_id");
    req.set_db_id(db_id);
    req.set_txn_id(txn_id2);
    req.add_mow_table_ids(table_id);
    CommitTxnResponse res;
    meta_service->commit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                             &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    std::string ver_key = partition_version_key({instance_id, db_id, table_id, t1p1});
    std::string ver_val;
    VersionPB version_pb;
    auto ret = txn->get(ver_key, &ver_val);
    ASSERT_EQ(ret, TxnErrorCode::TXN_OK);
    ASSERT_TRUE(version_pb.ParseFromString(ver_val));
    ASSERT_EQ(version_pb.version(), cur_max_version + 1);

    std::string lock_key = meta_delete_bitmap_update_lock_key({instance_id, table_id, -1});
    std::string lock_val;
    ret = txn->get(lock_key, &lock_val);
    ASSERT_EQ(ret, TxnErrorCode::TXN_KEY_NOT_FOUND);

    // txn1 retries to publish and gains the lock, try to publish with version 102
    lock_id = txn_id1;
    get_delete_bitmap_update_lock(meta_service.get(), table_id, t1p1, lock_id, initiator);

    // txn1's previous calculation task finshes and try to update delete bitmap with version 101
    std::string data2 = "5678";
    update_delete_bitmap(meta_service.get(), update_delete_bitmap_req, update_delete_bitmap_res,
                         table_id, t1p1, lock_id, initiator, tablet_id, txn_id1,
                         txn1_version_to_publish, data2);
    // this should fail
    ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::VERSION_NOT_MATCH);

    GetDeleteBitmapRequest get_delete_bitmap_req;
    GetDeleteBitmapResponse get_delete_bitmap_res;
    get_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
    get_delete_bitmap_req.set_tablet_id(tablet_id);
    get_delete_bitmap_req.add_rowset_ids("123");
    get_delete_bitmap_req.add_begin_versions(0);
    get_delete_bitmap_req.add_end_versions(cur_max_version + 1);
    meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                    &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
    ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(get_delete_bitmap_res.rowset_ids_size(), 1);
    ASSERT_EQ(get_delete_bitmap_res.versions_size(), 1);
    ASSERT_EQ(get_delete_bitmap_res.segment_ids_size(), 1);
    ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), 1);
    ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(0), data1);
}

TEST(MetaServiceTest, UpdateDeleteBitmapScOverrideExistingKey) {
    auto meta_service = get_meta_service();
    brpc::Controller cntl;
    size_t split_size = 90 * 1000; // see cloud/src/common/util.h

    extern std::string get_instance_id(const std::shared_ptr<ResourceManager>& rc_mgr,
                                       const std::string& cloud_unique_id);
    auto instance_id = get_instance_id(meta_service->resource_mgr(), "test_cloud_unique_id");

    {
        // schema change should use pending delete bitmap to clear previous failed trials
        int64_t db_id = 99999;
        int64_t table_id = 1801;
        int64_t index_id = 4801;
        int64_t t1p1 = 2001;
        int64_t tablet_id = 3001;
        int64_t txn_id;
        ASSERT_NO_FATAL_FAILURE(create_tablet_with_db_id(meta_service.get(), db_id, table_id,
                                                         index_id, t1p1, tablet_id));
        begin_txn_and_commit_rowset(meta_service.get(), "label11", db_id, table_id, t1p1, tablet_id,
                                    &txn_id);
        int64_t lock_id = -2;
        int64_t initiator = 1009;
        int64_t version = 100;

        get_delete_bitmap_update_lock(meta_service.get(), table_id, t1p1, lock_id, initiator);

        {
            UpdateDeleteBitmapRequest update_delete_bitmap_req;
            UpdateDeleteBitmapResponse update_delete_bitmap_res;
            // will be splited and stored in 5 KVs
            std::string data1(split_size * 5, 'c');
            update_delete_bitmap(meta_service.get(), update_delete_bitmap_req,
                                 update_delete_bitmap_res, table_id, t1p1, lock_id, initiator,
                                 tablet_id, txn_id, version, data1);
            ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);

            GetDeleteBitmapRequest get_delete_bitmap_req;
            GetDeleteBitmapResponse get_delete_bitmap_res;
            get_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
            get_delete_bitmap_req.set_tablet_id(tablet_id);
            get_delete_bitmap_req.add_rowset_ids("123");
            get_delete_bitmap_req.add_begin_versions(0);
            get_delete_bitmap_req.add_end_versions(version);
            meta_service->get_delete_bitmap(
                    reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                    &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
            ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(get_delete_bitmap_res.rowset_ids_size(), 1);
            ASSERT_EQ(get_delete_bitmap_res.versions_size(), 1);
            ASSERT_EQ(get_delete_bitmap_res.segment_ids_size(), 1);
            ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), 1);
            ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(0), data1);
        }

        {
            std::string pending_key = meta_pending_delete_bitmap_key({instance_id, tablet_id});
            std::string pending_val;
            std::unique_ptr<Transaction> txn;
            ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
            ASSERT_EQ(txn->get(pending_key, &pending_val), TxnErrorCode::TXN_OK);
            PendingDeleteBitmapPB pending_info;
            ASSERT_TRUE(pending_info.ParseFromString(pending_val));
            ASSERT_EQ(pending_info.delete_bitmap_keys_size(), 1);

            std::string_view k1 = pending_info.delete_bitmap_keys(0);
            k1.remove_prefix(1);
            std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
            decode_key(&k1, &out);
            // 0x01 "meta" ${instance_id} "delete_bitmap" ${tablet_id} ${rowset_id} ${version} ${segment_id} -> roaringbitmap
            auto encoded_tablet_id = std::get<std::int64_t>(std::get<0>(out[3]));
            ASSERT_EQ(encoded_tablet_id, tablet_id);
            auto encoded_rowset_id = std::get<std::string>(std::get<0>(out[4]));
            ASSERT_EQ(encoded_rowset_id, "123");
            auto encoded_version = std::get<std::int64_t>(std::get<0>(out[5]));
            ASSERT_EQ(encoded_version, version);
            auto encoded_segment_id = std::get<std::int64_t>(std::get<0>(out[6]));
            ASSERT_EQ(encoded_segment_id, 0);
        }

        {
            UpdateDeleteBitmapRequest update_delete_bitmap_req;
            UpdateDeleteBitmapResponse update_delete_bitmap_res;
            // will be splited and stored in 3 KVs
            // if we don't remove previous splited KVs, will crash when reading
            std::string data2(split_size * 3, 'a');
            update_delete_bitmap(meta_service.get(), update_delete_bitmap_req,
                                 update_delete_bitmap_res, table_id, t1p1, lock_id, initiator,
                                 tablet_id, txn_id, version, data2);
            ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);

            GetDeleteBitmapRequest get_delete_bitmap_req;
            GetDeleteBitmapResponse get_delete_bitmap_res;
            get_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
            get_delete_bitmap_req.set_tablet_id(tablet_id);
            get_delete_bitmap_req.add_rowset_ids("123");
            get_delete_bitmap_req.add_begin_versions(0);
            get_delete_bitmap_req.add_end_versions(version);
            meta_service->get_delete_bitmap(
                    reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                    &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
            ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(get_delete_bitmap_res.rowset_ids_size(), 1);
            ASSERT_EQ(get_delete_bitmap_res.versions_size(), 1);
            ASSERT_EQ(get_delete_bitmap_res.segment_ids_size(), 1);
            ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), 1);
            ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(0), data2);
        }
    }
}

void testUpdateDeleteBitmap(int lock_version) {
    config::delete_bitmap_lock_v2_white_list = lock_version == 1 ? "" : "*";
    auto meta_service = get_meta_service();
    remove_delete_bitmap_lock(meta_service.get(), 112);

    extern std::string get_instance_id(const std::shared_ptr<ResourceManager>& rc_mgr,
                                       const std::string& cloud_unique_id);
    auto instance_id = get_instance_id(meta_service->resource_mgr(), "test_cloud_unique_id");

    // get delete bitmap update lock
    brpc::Controller cntl;
    GetDeleteBitmapUpdateLockRequest get_lock_req;
    GetDeleteBitmapUpdateLockResponse get_lock_res;
    get_lock_req.set_cloud_unique_id("test_cloud_unique_id");
    get_lock_req.set_table_id(112);
    get_lock_req.add_partition_ids(123);
    get_lock_req.set_expiration(5);
    get_lock_req.set_lock_id(888);
    get_lock_req.set_initiator(-1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &get_lock_req,
            &get_lock_res, nullptr);
    ASSERT_EQ(get_lock_res.status().code(), MetaServiceCode::OK);

    // first update delete bitmap
    std::vector<std::string> delete_bitmap_keys;
    {
        UpdateDeleteBitmapRequest update_delete_bitmap_req;
        UpdateDeleteBitmapResponse update_delete_bitmap_res;
        update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
        update_delete_bitmap_req.set_table_id(112);
        update_delete_bitmap_req.set_partition_id(123);
        update_delete_bitmap_req.set_lock_id(888);
        update_delete_bitmap_req.set_initiator(-1);
        update_delete_bitmap_req.set_tablet_id(333);

        update_delete_bitmap_req.add_rowset_ids("123");
        update_delete_bitmap_req.add_segment_ids(1);
        update_delete_bitmap_req.add_versions(2);
        update_delete_bitmap_req.add_segment_delete_bitmaps("abc0");
        delete_bitmap_keys.emplace_back(meta_delete_bitmap_key({instance_id, 333, "123", 2, 1}));

        update_delete_bitmap_req.add_rowset_ids("123");
        update_delete_bitmap_req.add_segment_ids(0);
        update_delete_bitmap_req.add_versions(3);
        update_delete_bitmap_req.add_segment_delete_bitmaps("abc1");
        delete_bitmap_keys.emplace_back(meta_delete_bitmap_key({instance_id, 333, "123", 3, 0}));

        update_delete_bitmap_req.add_rowset_ids("123");
        update_delete_bitmap_req.add_segment_ids(1);
        update_delete_bitmap_req.add_versions(3);
        update_delete_bitmap_req.add_segment_delete_bitmaps("abc2");
        delete_bitmap_keys.emplace_back(meta_delete_bitmap_key({instance_id, 333, "123", 3, 1}));

        update_delete_bitmap_req.add_rowset_ids("124");
        update_delete_bitmap_req.add_segment_ids(0);
        update_delete_bitmap_req.add_versions(2);
        update_delete_bitmap_req.add_segment_delete_bitmaps("abc3");
        delete_bitmap_keys.emplace_back(meta_delete_bitmap_key({instance_id, 333, "124", 2, 0}));

        std::string large_value = generate_random_string(300 * 1000);
        update_delete_bitmap_req.add_rowset_ids("124");
        update_delete_bitmap_req.add_segment_ids(1);
        update_delete_bitmap_req.add_versions(2);
        update_delete_bitmap_req.add_segment_delete_bitmaps(large_value);
        delete_bitmap_keys.emplace_back(meta_delete_bitmap_key({instance_id, 333, "124", 2, 1}));

        update_delete_bitmap_req.add_rowset_ids("124");
        update_delete_bitmap_req.add_segment_ids(0);
        update_delete_bitmap_req.add_versions(3);
        update_delete_bitmap_req.add_segment_delete_bitmaps("abc4");
        delete_bitmap_keys.emplace_back(meta_delete_bitmap_key({instance_id, 333, "124", 3, 0}));

        // v2 update delete bitmap
        {
            DeleteBitmapPB delete_bitmap;
            delete_bitmap.add_rowset_ids("123");
            delete_bitmap.add_segment_ids(1);
            delete_bitmap.add_versions(2);
            delete_bitmap.add_segment_delete_bitmaps("abc0");

            delete_bitmap.add_rowset_ids("123");
            delete_bitmap.add_segment_ids(0);
            delete_bitmap.add_versions(3);
            delete_bitmap.add_segment_delete_bitmaps("abc1");

            delete_bitmap.add_rowset_ids("123");
            delete_bitmap.add_segment_ids(1);
            delete_bitmap.add_versions(3);
            delete_bitmap.add_segment_delete_bitmaps("abc2");

            DeleteBitmapStoragePB delete_bitmap_storage_pb;
            delete_bitmap_storage_pb.set_store_in_fdb(true);
            *(delete_bitmap_storage_pb.mutable_delete_bitmap()) = std::move(delete_bitmap);
            *(update_delete_bitmap_req.add_delete_bitmap_storages()) =
                    std::move(delete_bitmap_storage_pb);
            update_delete_bitmap_req.add_delta_rowset_ids("124");
            delete_bitmap_keys.emplace_back(
                    versioned::meta_delete_bitmap_key({instance_id, 333, "124"}));
        }
        {
            DeleteBitmapPB delete_bitmap;
            delete_bitmap.add_rowset_ids("124");
            delete_bitmap.add_segment_ids(0);
            delete_bitmap.add_versions(2);
            delete_bitmap.add_segment_delete_bitmaps("abc3");

            delete_bitmap.add_rowset_ids("124");
            delete_bitmap.add_segment_ids(1);
            delete_bitmap.add_versions(2);
            delete_bitmap.add_segment_delete_bitmaps(large_value);

            delete_bitmap.add_rowset_ids("124");
            delete_bitmap.add_segment_ids(0);
            delete_bitmap.add_versions(3);
            delete_bitmap.add_segment_delete_bitmaps("abc4");

            DeleteBitmapStoragePB delete_bitmap_storage_pb;
            delete_bitmap_storage_pb.set_store_in_fdb(true);
            *(delete_bitmap_storage_pb.mutable_delete_bitmap()) = std::move(delete_bitmap);
            *(update_delete_bitmap_req.add_delete_bitmap_storages()) =
                    std::move(delete_bitmap_storage_pb);
            update_delete_bitmap_req.add_delta_rowset_ids("125");
            delete_bitmap_keys.emplace_back(
                    versioned::meta_delete_bitmap_key({instance_id, 333, "125"}));
        }

        meta_service->update_delete_bitmap(
                reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                &update_delete_bitmap_req, &update_delete_bitmap_res, nullptr);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);

        // first get delete bitmap
        GetDeleteBitmapRequest get_delete_bitmap_req;
        GetDeleteBitmapResponse get_delete_bitmap_res;
        get_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
        get_delete_bitmap_req.set_tablet_id(333);

        get_delete_bitmap_req.add_rowset_ids("123");
        get_delete_bitmap_req.add_begin_versions(3);
        get_delete_bitmap_req.add_end_versions(3);

        get_delete_bitmap_req.add_rowset_ids("124");
        get_delete_bitmap_req.add_begin_versions(0);
        get_delete_bitmap_req.add_end_versions(3);

        get_delete_bitmap_req.add_rowset_ids("125");
        get_delete_bitmap_req.add_begin_versions(0);
        get_delete_bitmap_req.add_end_versions(3);

        meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                        &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
        auto check_v1_delete_bitmap = [&](GetDeleteBitmapResponse get_delete_bitmap_res) {
            ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(get_delete_bitmap_res.rowset_ids_size(), 5);
            ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), 5);
            ASSERT_EQ(get_delete_bitmap_res.versions_size(), 5);
            ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), 5);

            ASSERT_EQ(get_delete_bitmap_res.rowset_ids(0), "123");
            ASSERT_EQ(get_delete_bitmap_res.segment_ids(0), 0);
            ASSERT_EQ(get_delete_bitmap_res.versions(0), 3);
            ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(0), "abc1");

            ASSERT_EQ(get_delete_bitmap_res.rowset_ids(1), "123");
            ASSERT_EQ(get_delete_bitmap_res.segment_ids(1), 1);
            ASSERT_EQ(get_delete_bitmap_res.versions(1), 3);
            ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(1), "abc2");

            ASSERT_EQ(get_delete_bitmap_res.rowset_ids(2), "124");
            ASSERT_EQ(get_delete_bitmap_res.segment_ids(2), 0);
            ASSERT_EQ(get_delete_bitmap_res.versions(2), 2);
            ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(2), "abc3");

            ASSERT_EQ(get_delete_bitmap_res.rowset_ids(3), "124");
            ASSERT_EQ(get_delete_bitmap_res.segment_ids(3), 1);
            ASSERT_EQ(get_delete_bitmap_res.versions(3), 2);
            ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(3), large_value);

            ASSERT_EQ(get_delete_bitmap_res.rowset_ids(4), "124");
            ASSERT_EQ(get_delete_bitmap_res.segment_ids(4), 0);
            ASSERT_EQ(get_delete_bitmap_res.versions(4), 3);
            ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(4), "abc4");
        };
        check_v1_delete_bitmap(get_delete_bitmap_res);

        // v2 get delete bitmap
        auto check_v2_delete_bitmap = [&](GetDeleteBitmapResponse& get_delete_bitmap_res2) {
            ASSERT_EQ(get_delete_bitmap_res2.status().code(), MetaServiceCode::OK);
            ASSERT_EQ(get_delete_bitmap_res2.delete_bitmap_storages_size(), 2);
            {
                auto& delete_bitmap_storage = get_delete_bitmap_res2.delete_bitmap_storages(0);
                ASSERT_TRUE(delete_bitmap_storage.store_in_fdb());
                auto& delete_bitmap_pb = delete_bitmap_storage.delete_bitmap();
                ASSERT_EQ(delete_bitmap_pb.rowset_ids_size(), 3);
                ASSERT_EQ(delete_bitmap_pb.versions_size(), 3);
                ASSERT_EQ(delete_bitmap_pb.segment_ids_size(), 3);
                ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps_size(), 3);

                ASSERT_EQ(delete_bitmap_pb.rowset_ids(0), "123");
                ASSERT_EQ(delete_bitmap_pb.segment_ids(0), 1);
                ASSERT_EQ(delete_bitmap_pb.versions(0), 2);
                ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps(0), "abc0");

                ASSERT_EQ(delete_bitmap_pb.rowset_ids(1), "123");
                ASSERT_EQ(delete_bitmap_pb.segment_ids(1), 0);
                ASSERT_EQ(delete_bitmap_pb.versions(1), 3);
                ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps(1), "abc1");

                ASSERT_EQ(delete_bitmap_pb.rowset_ids(2), "123");
                ASSERT_EQ(delete_bitmap_pb.segment_ids(2), 1);
                ASSERT_EQ(delete_bitmap_pb.versions(2), 3);
                ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps(2), "abc2");
            }
            {
                auto& delete_bitmap_storage = get_delete_bitmap_res2.delete_bitmap_storages(1);
                ASSERT_TRUE(delete_bitmap_storage.store_in_fdb());
                auto& delete_bitmap_pb = delete_bitmap_storage.delete_bitmap();
                ASSERT_EQ(delete_bitmap_pb.rowset_ids_size(), 3);
                ASSERT_EQ(delete_bitmap_pb.versions_size(), 3);
                ASSERT_EQ(delete_bitmap_pb.segment_ids_size(), 3);
                ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps_size(), 3);

                ASSERT_EQ(delete_bitmap_pb.rowset_ids(0), "124");
                ASSERT_EQ(delete_bitmap_pb.segment_ids(0), 0);
                ASSERT_EQ(delete_bitmap_pb.versions(0), 2);
                ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps(0), "abc3");

                ASSERT_EQ(delete_bitmap_pb.rowset_ids(1), "124");
                ASSERT_EQ(delete_bitmap_pb.segment_ids(1), 1);
                ASSERT_EQ(delete_bitmap_pb.versions(1), 2);
                ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps(1), large_value);

                ASSERT_EQ(delete_bitmap_pb.rowset_ids(2), "124");
                ASSERT_EQ(delete_bitmap_pb.segment_ids(2), 0);
                ASSERT_EQ(delete_bitmap_pb.versions(2), 3);
                ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps(2), "abc4");
            }
        };
        {
            GetDeleteBitmapResponse get_delete_bitmap_res2;
            get_delete_bitmap_req.set_store_version(2);

            meta_service->get_delete_bitmap(
                    reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                    &get_delete_bitmap_req, &get_delete_bitmap_res2, nullptr);
            check_v2_delete_bitmap(get_delete_bitmap_res2);
        }

        // v1 and v2 get delete bitmap
        {
            GetDeleteBitmapResponse get_delete_bitmap_res3;
            get_delete_bitmap_req.set_store_version(3);

            meta_service->get_delete_bitmap(
                    reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                    &get_delete_bitmap_req, &get_delete_bitmap_res3, nullptr);
            check_v1_delete_bitmap(get_delete_bitmap_res3);
            check_v2_delete_bitmap(get_delete_bitmap_res3);
        }

        // check pending delete bitmap key
        {
            std::string pending_key = meta_pending_delete_bitmap_key({instance_id, 333});
            std::string pending_val;
            std::unique_ptr<Transaction> txn;
            ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
            ASSERT_EQ(txn->get(pending_key, &pending_val), TxnErrorCode::TXN_OK);
            PendingDeleteBitmapPB pending_info;
            ASSERT_TRUE(pending_info.ParseFromString(pending_val));
            ASSERT_EQ(pending_info.delete_bitmap_keys_size(), delete_bitmap_keys.size());
            for (size_t i = 0; i < delete_bitmap_keys.size(); ++i) {
                ASSERT_EQ(pending_info.delete_bitmap_keys(i), delete_bitmap_keys[i]);
            }
        }
    }

    // second update delete bitmap
    {
        UpdateDeleteBitmapRequest update_delete_bitmap_req;
        UpdateDeleteBitmapResponse update_delete_bitmap_res;
        update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
        update_delete_bitmap_req.set_table_id(112);
        update_delete_bitmap_req.set_partition_id(123);
        update_delete_bitmap_req.set_lock_id(888);
        update_delete_bitmap_req.set_initiator(-1);
        update_delete_bitmap_req.set_tablet_id(333);

        std::string large_value = generate_random_string(200 * 1000);
        update_delete_bitmap_req.add_rowset_ids("123");
        update_delete_bitmap_req.add_segment_ids(0);
        update_delete_bitmap_req.add_versions(2);
        update_delete_bitmap_req.add_segment_delete_bitmaps(large_value);

        update_delete_bitmap_req.add_rowset_ids("123");
        update_delete_bitmap_req.add_segment_ids(1);
        update_delete_bitmap_req.add_versions(2);
        update_delete_bitmap_req.add_segment_delete_bitmaps("bbb0");

        update_delete_bitmap_req.add_rowset_ids("123");
        update_delete_bitmap_req.add_segment_ids(1);
        update_delete_bitmap_req.add_versions(3);
        update_delete_bitmap_req.add_segment_delete_bitmaps("bbb1");

        update_delete_bitmap_req.add_rowset_ids("124");
        update_delete_bitmap_req.add_segment_ids(1);
        update_delete_bitmap_req.add_versions(3);
        update_delete_bitmap_req.add_segment_delete_bitmaps("bbb2");

        meta_service->update_delete_bitmap(
                reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                &update_delete_bitmap_req, &update_delete_bitmap_res, nullptr);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);

        // second get delete bitmap
        GetDeleteBitmapRequest get_delete_bitmap_req;
        GetDeleteBitmapResponse get_delete_bitmap_res;
        get_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
        get_delete_bitmap_req.set_tablet_id(333);

        get_delete_bitmap_req.add_rowset_ids("123");
        get_delete_bitmap_req.add_begin_versions(0);
        get_delete_bitmap_req.add_end_versions(3);

        get_delete_bitmap_req.add_rowset_ids("124");
        get_delete_bitmap_req.add_begin_versions(0);
        get_delete_bitmap_req.add_end_versions(3);

        meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                        &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
        ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(get_delete_bitmap_res.rowset_ids_size(), 4);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), 4);
        ASSERT_EQ(get_delete_bitmap_res.versions_size(), 4);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), 4);

        ASSERT_EQ(get_delete_bitmap_res.rowset_ids(0), "123");
        ASSERT_EQ(get_delete_bitmap_res.segment_ids(0), 0);
        ASSERT_EQ(get_delete_bitmap_res.versions(0), 2);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(0), large_value);

        ASSERT_EQ(get_delete_bitmap_res.rowset_ids(1), "123");
        ASSERT_EQ(get_delete_bitmap_res.segment_ids(1), 1);
        ASSERT_EQ(get_delete_bitmap_res.versions(1), 2);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(1), "bbb0");

        ASSERT_EQ(get_delete_bitmap_res.rowset_ids(2), "123");
        ASSERT_EQ(get_delete_bitmap_res.segment_ids(2), 1);
        ASSERT_EQ(get_delete_bitmap_res.versions(2), 3);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(2), "bbb1");

        ASSERT_EQ(get_delete_bitmap_res.rowset_ids(3), "124");
        ASSERT_EQ(get_delete_bitmap_res.segment_ids(3), 1);
        ASSERT_EQ(get_delete_bitmap_res.versions(3), 3);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(3), "bbb2");
    }

    // large size txn
    {
        UpdateDeleteBitmapRequest update_delete_bitmap_req;
        UpdateDeleteBitmapResponse update_delete_bitmap_res;
        update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
        update_delete_bitmap_req.set_table_id(112);
        update_delete_bitmap_req.set_partition_id(123);
        update_delete_bitmap_req.set_lock_id(888);
        update_delete_bitmap_req.set_initiator(-1);
        update_delete_bitmap_req.set_tablet_id(333);

        std::string large_value = generate_random_string(300 * 1000);
        for (size_t i = 0; i < 100; ++i) {
            update_delete_bitmap_req.add_rowset_ids("123");
            update_delete_bitmap_req.add_segment_ids(1);
            update_delete_bitmap_req.add_versions(i);
            update_delete_bitmap_req.add_segment_delete_bitmaps(large_value);
        }

        update_delete_bitmap_req.add_rowset_ids("124");
        update_delete_bitmap_req.add_segment_ids(0);
        update_delete_bitmap_req.add_versions(3);
        update_delete_bitmap_req.add_segment_delete_bitmaps("abcd4");

        meta_service->update_delete_bitmap(
                reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                &update_delete_bitmap_req, &update_delete_bitmap_res, nullptr);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);

        GetDeleteBitmapRequest get_delete_bitmap_req;
        GetDeleteBitmapResponse get_delete_bitmap_res;
        get_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
        get_delete_bitmap_req.set_tablet_id(333);

        get_delete_bitmap_req.add_rowset_ids("123");
        get_delete_bitmap_req.add_begin_versions(0);
        get_delete_bitmap_req.add_end_versions(101);

        get_delete_bitmap_req.add_rowset_ids("124");
        get_delete_bitmap_req.add_begin_versions(0);
        get_delete_bitmap_req.add_end_versions(3);

        meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                        &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
        ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(get_delete_bitmap_res.rowset_ids_size(), 101);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), 101);
        ASSERT_EQ(get_delete_bitmap_res.versions_size(), 101);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), 101);

        for (size_t i = 0; i < 100; ++i) {
            ASSERT_EQ(get_delete_bitmap_res.rowset_ids(i), "123");
            ASSERT_EQ(get_delete_bitmap_res.segment_ids(i), 1);
            ASSERT_EQ(get_delete_bitmap_res.versions(i), i);
            ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(i), large_value);
        }

        ASSERT_EQ(get_delete_bitmap_res.rowset_ids(100), "124");
        ASSERT_EQ(get_delete_bitmap_res.segment_ids(100), 0);
        ASSERT_EQ(get_delete_bitmap_res.versions(100), 3);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(100), "abcd4");
    }

    // update existing delete bitmap key
    {
        //first update new key
        UpdateDeleteBitmapRequest update_delete_bitmap_req;
        UpdateDeleteBitmapResponse update_delete_bitmap_res;
        update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
        update_delete_bitmap_req.set_table_id(112);
        update_delete_bitmap_req.set_partition_id(123);
        update_delete_bitmap_req.set_lock_id(888);
        update_delete_bitmap_req.set_initiator(-1);
        update_delete_bitmap_req.set_tablet_id(333);
        std::string large_value = generate_random_string(300 * 1000 * 3);
        update_delete_bitmap_req.add_rowset_ids("456");
        update_delete_bitmap_req.add_segment_ids(0);
        update_delete_bitmap_req.add_versions(2);
        update_delete_bitmap_req.add_segment_delete_bitmaps(large_value);
        meta_service->update_delete_bitmap(
                reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                &update_delete_bitmap_req, &update_delete_bitmap_res, nullptr);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);

        GetDeleteBitmapRequest get_delete_bitmap_req;
        GetDeleteBitmapResponse get_delete_bitmap_res;
        get_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
        get_delete_bitmap_req.set_tablet_id(333);

        get_delete_bitmap_req.add_rowset_ids("456");
        get_delete_bitmap_req.add_begin_versions(2);
        get_delete_bitmap_req.add_end_versions(2);

        meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                        &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
        ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(get_delete_bitmap_res.rowset_ids_size(), 1);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), 1);
        ASSERT_EQ(get_delete_bitmap_res.versions_size(), 1);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), 1);

        ASSERT_EQ(get_delete_bitmap_res.rowset_ids(0), "456");
        ASSERT_EQ(get_delete_bitmap_res.segment_ids(0), 0);
        ASSERT_EQ(get_delete_bitmap_res.versions(0), 2);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(0), large_value);
    }

    RemoveDeleteBitmapUpdateLockRequest remove_lock_req;
    RemoveDeleteBitmapUpdateLockResponse remove_lock_res;
    remove_lock_req.set_cloud_unique_id("test_cloud_unique_id");
    remove_lock_req.set_table_id(112);
    remove_lock_req.set_lock_id(888);
    remove_lock_req.set_initiator(-1);
    meta_service->remove_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &remove_lock_req,
            &remove_lock_res, nullptr);
    ASSERT_EQ(remove_lock_res.status().code(), MetaServiceCode::OK);

    for (int i = 0; i < 2; ++i) {
        auto lock_id =
                i == 0 ? COMPACTION_DELETE_BITMAP_LOCK_ID : SCHEMA_CHANGE_DELETE_BITMAP_LOCK_ID;
        // case: compaction or schema_change update delete bitmap
        get_lock_req.set_lock_id(lock_id);
        get_lock_req.set_initiator(800);
        meta_service->get_delete_bitmap_update_lock(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &get_lock_req,
                &get_lock_res, nullptr);
        ASSERT_EQ(get_lock_res.status().code(), MetaServiceCode::OK);
        // update delete bitmap
        UpdateDeleteBitmapRequest update_delete_bitmap_req;
        UpdateDeleteBitmapResponse update_delete_bitmap_res;
        update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
        update_delete_bitmap_req.set_table_id(112);
        update_delete_bitmap_req.set_partition_id(123);
        update_delete_bitmap_req.set_lock_id(lock_id);
        update_delete_bitmap_req.set_initiator(800);
        update_delete_bitmap_req.set_tablet_id(333);
        update_delete_bitmap_req.add_rowset_ids("123");
        update_delete_bitmap_req.add_segment_ids(0);
        update_delete_bitmap_req.add_versions(2);
        update_delete_bitmap_req.add_segment_delete_bitmaps("compaction0");
        meta_service->update_delete_bitmap(
                reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                &update_delete_bitmap_req, &update_delete_bitmap_res, nullptr);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);
        // remove lock
        remove_lock_req.set_lock_id(lock_id);
        remove_lock_req.set_initiator(800);
        meta_service->remove_delete_bitmap_update_lock(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &remove_lock_req,
                &remove_lock_res, nullptr);
        ASSERT_EQ(remove_lock_res.status().code(), MetaServiceCode::OK);

        // case: compaction or schema_change update delete bitmap with lock expired
        get_lock_req.set_lock_id(lock_id);
        get_lock_req.set_initiator(800);
        get_lock_req.set_expiration(1);
        meta_service->get_delete_bitmap_update_lock(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &get_lock_req,
                &get_lock_res, nullptr);
        ASSERT_EQ(get_lock_res.status().code(), MetaServiceCode::OK);
        // load get lock
        sleep(2);
        get_lock_req.set_lock_id(100);
        get_lock_req.set_initiator(-1);
        get_lock_req.set_expiration(1);
        meta_service->get_delete_bitmap_update_lock(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &get_lock_req,
                &get_lock_res, nullptr);
        ASSERT_EQ(get_lock_res.status().code(), MetaServiceCode::OK);
        // compaction update delete bitmap
        meta_service->update_delete_bitmap(
                reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                &update_delete_bitmap_req, &update_delete_bitmap_res, nullptr);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::LOCK_EXPIRED);

        // case: compaction2 or schema_change2 get lock
        sleep(2);
        get_lock_req.set_lock_id(lock_id);
        get_lock_req.set_initiator(810);
        get_lock_req.set_expiration(1);
        meta_service->get_delete_bitmap_update_lock(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &get_lock_req,
                &get_lock_res, nullptr);
        ASSERT_EQ(get_lock_res.status().code(), MetaServiceCode::OK);
        // compaction1 update delete bitmap
        meta_service->update_delete_bitmap(
                reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                &update_delete_bitmap_req, &update_delete_bitmap_res, nullptr);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::LOCK_EXPIRED);
        // remove compaction2 or or schema_change2 lock
        remove_lock_req.set_lock_id(lock_id);
        remove_lock_req.set_initiator(810);
        meta_service->remove_delete_bitmap_update_lock(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &remove_lock_req,
                &remove_lock_res, nullptr);
        ASSERT_EQ(remove_lock_res.status().code(), MetaServiceCode::OK);
    }

    {
        //compaction update delete bitmap without lock
        UpdateDeleteBitmapRequest update_delete_bitmap_req;
        UpdateDeleteBitmapResponse update_delete_bitmap_res;
        update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
        update_delete_bitmap_req.set_table_id(112);
        update_delete_bitmap_req.set_partition_id(123);
        update_delete_bitmap_req.set_without_lock(true);
        update_delete_bitmap_req.set_lock_id(-3);
        update_delete_bitmap_req.set_initiator(-1);
        update_delete_bitmap_req.set_tablet_id(333);
        std::string large_value = generate_random_string(300 * 1000);
        update_delete_bitmap_req.add_rowset_ids("456");
        update_delete_bitmap_req.add_segment_ids(0);
        update_delete_bitmap_req.add_versions(2);
        update_delete_bitmap_req.add_segment_delete_bitmaps(large_value);
        meta_service->update_delete_bitmap(
                reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                &update_delete_bitmap_req, &update_delete_bitmap_res, nullptr);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);

        GetDeleteBitmapRequest get_delete_bitmap_req;
        GetDeleteBitmapResponse get_delete_bitmap_res;
        get_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
        get_delete_bitmap_req.set_tablet_id(333);

        get_delete_bitmap_req.add_rowset_ids("456");
        get_delete_bitmap_req.add_begin_versions(2);
        get_delete_bitmap_req.add_end_versions(2);

        meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                        &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
        ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(get_delete_bitmap_res.rowset_ids_size(), 1);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), 1);
        ASSERT_EQ(get_delete_bitmap_res.versions_size(), 1);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), 1);

        ASSERT_EQ(get_delete_bitmap_res.rowset_ids(0), "456");
        ASSERT_EQ(get_delete_bitmap_res.segment_ids(0), 0);
        ASSERT_EQ(get_delete_bitmap_res.versions(0), 2);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(0), large_value);
    }

    remove_delete_bitmap_lock(meta_service.get(), 112);
}

TEST(MetaServiceTest, UpdateDeleteBitmap) {
    testUpdateDeleteBitmap(2);
    testUpdateDeleteBitmap(1);
}

TEST(MetaServiceTest, UpdateDeleteBitmapWithException) {
    auto meta_service = get_meta_service();
    brpc::Controller cntl;

    {
        UpdateDeleteBitmapRequest update_delete_bitmap_req;
        UpdateDeleteBitmapResponse update_delete_bitmap_res;
        update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
        update_delete_bitmap_req.set_table_id(112);
        update_delete_bitmap_req.set_partition_id(123);
        update_delete_bitmap_req.set_lock_id(888);
        update_delete_bitmap_req.set_initiator(-1);
        update_delete_bitmap_req.set_tablet_id(333);

        update_delete_bitmap_req.add_rowset_ids("123");
        update_delete_bitmap_req.add_segment_ids(1);
        update_delete_bitmap_req.add_versions(2);
        update_delete_bitmap_req.add_segment_delete_bitmaps("abc0");

        meta_service->update_delete_bitmap(
                reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                &update_delete_bitmap_req, &update_delete_bitmap_res, nullptr);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::LOCK_EXPIRED);
    }

    // get delete bitmap update lock
    GetDeleteBitmapUpdateLockRequest get_lock_req;
    GetDeleteBitmapUpdateLockResponse get_lock_res;
    get_lock_req.set_cloud_unique_id("test_cloud_unique_id");
    get_lock_req.set_table_id(112);
    get_lock_req.add_partition_ids(123);
    get_lock_req.set_expiration(5);
    get_lock_req.set_lock_id(888);
    get_lock_req.set_initiator(-1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &get_lock_req,
            &get_lock_res, nullptr);
    ASSERT_EQ(get_lock_res.status().code(), MetaServiceCode::OK);

    {
        UpdateDeleteBitmapRequest update_delete_bitmap_req;
        UpdateDeleteBitmapResponse update_delete_bitmap_res;
        update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
        update_delete_bitmap_req.set_table_id(112);
        update_delete_bitmap_req.set_partition_id(123);
        update_delete_bitmap_req.set_lock_id(222);
        update_delete_bitmap_req.set_initiator(-1);
        update_delete_bitmap_req.set_tablet_id(333);

        update_delete_bitmap_req.add_rowset_ids("123");
        update_delete_bitmap_req.add_segment_ids(1);
        update_delete_bitmap_req.add_versions(2);
        update_delete_bitmap_req.add_segment_delete_bitmaps("abc0");

        meta_service->update_delete_bitmap(
                reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                &update_delete_bitmap_req, &update_delete_bitmap_res, nullptr);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::LOCK_EXPIRED);
    }

    {
        UpdateDeleteBitmapRequest update_delete_bitmap_req;
        UpdateDeleteBitmapResponse update_delete_bitmap_res;
        update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
        update_delete_bitmap_req.set_table_id(112);
        update_delete_bitmap_req.set_partition_id(123);
        update_delete_bitmap_req.set_lock_id(888);
        update_delete_bitmap_req.set_initiator(-2);
        update_delete_bitmap_req.set_tablet_id(333);

        update_delete_bitmap_req.add_rowset_ids("123");
        update_delete_bitmap_req.add_segment_ids(1);
        update_delete_bitmap_req.add_versions(2);
        update_delete_bitmap_req.add_segment_delete_bitmaps("abc0");

        meta_service->update_delete_bitmap(
                reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                &update_delete_bitmap_req, &update_delete_bitmap_res, nullptr);
        ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::LOCK_EXPIRED);
    }
}

void update_delete_bitmap_with_remove_pre(MetaServiceProxy* meta_service, int64_t table_id,
                                          int64_t tablet_id, bool inject = false,
                                          bool rowset_non_exist = false) {
    // create rowset, if `rowset_non_exist` enabled, only r4 exists
    {
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        if (rowset_non_exist) {
            std::string rs_key, rs_val;
            doris::RowsetMetaCloudPB rs;
            rs.set_rowset_id(0);

            rs_key = meta_rowset_key({"test_instance", tablet_id, 3});
            rs.set_rowset_id_v2("r2-3");
            ASSERT_TRUE(rs.SerializeToString(&rs_val));
            txn->put(rs_key, rs_val);

            rs_key = meta_rowset_key({"test_instance", tablet_id, 4});
            rs.set_rowset_id_v2("r4");
            ASSERT_TRUE(rs.SerializeToString(&rs_val));
            txn->put(rs_key, rs_val);
        } else {
            for (int i = 2; i <= 4; i++) {
                std::string rs_key, rs_val;
                MetaRowsetKeyInfo rs_key_info {"test_instance", tablet_id, i};
                meta_rowset_key(rs_key_info, &rs_key);
                doris::RowsetMetaCloudPB rs;
                rs.set_rowset_id(0);
                rs.set_rowset_id_v2("r" + std::to_string(i));
                ASSERT_TRUE(rs.SerializeToString(&rs_val));
                txn->put(rs_key, rs_val);
            }
        }
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    }
    brpc::Controller cntl;
    // compaction update delete bitmap with remove pre rowset delete bitmaps
    // get update lock
    GetDeleteBitmapUpdateLockRequest get_lock_req;
    GetDeleteBitmapUpdateLockResponse get_lock_res;
    get_lock_req.set_cloud_unique_id("test_cloud_unique_id");
    get_lock_req.set_table_id(table_id);
    get_lock_req.set_lock_id(-1);
    get_lock_req.set_initiator(203);
    get_lock_req.set_expiration(10);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &get_lock_req,
            &get_lock_res, nullptr);
    ASSERT_EQ(get_lock_res.status().code(), MetaServiceCode::OK);
    // write delete bitmap
    UpdateDeleteBitmapRequest update_delete_bitmap_req;
    UpdateDeleteBitmapResponse update_delete_bitmap_res;
    update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
    update_delete_bitmap_req.set_table_id(table_id);
    update_delete_bitmap_req.set_partition_id(201);
    update_delete_bitmap_req.set_tablet_id(tablet_id);
    update_delete_bitmap_req.set_lock_id(-1);
    update_delete_bitmap_req.set_initiator(203);
    std::string large_value = generate_random_string(300 * 1000);
    std::vector<std::tuple<std::string, int64_t, int64_t>> rowset_segment_version_vector = {
            /* r2-0 */ {"r2", 0, 3}, {"r2", 0, 4}, {"r2", 0, 5}, {"r2", 0, 6},
            /* r3-0 */ {"r3", 0, 4}, {"r3", 0, 5}, {"r3", 0, 6},
            /* r3-1 */ {"r3", 1, 4}, {"r3", 1, 5},
            /* r3-2 */ {"r3", 2, 4}, {"r3", 2, 6},
            /* r4-0 */ {"r4", 0, 5}, {"r4", 0, 6}};
    for (const auto& [rowset, segment, version] : rowset_segment_version_vector) {
        update_delete_bitmap_req.add_rowset_ids(rowset);
        update_delete_bitmap_req.add_segment_ids(segment);
        update_delete_bitmap_req.add_versions(version);
        update_delete_bitmap_req.add_segment_delete_bitmaps(large_value);
    }
    meta_service->update_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                       &update_delete_bitmap_req, &update_delete_bitmap_res,
                                       nullptr);
    ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);
    // remove delete bitmap lock
    RemoveDeleteBitmapUpdateLockRequest remove_lock_req;
    RemoveDeleteBitmapUpdateLockResponse remove_lock_res;
    remove_lock_req.set_cloud_unique_id("test_cloud_unique_id");
    remove_lock_req.set_table_id(table_id);
    remove_lock_req.set_lock_id(-1);
    remove_lock_req.set_initiator(203);
    meta_service->remove_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &remove_lock_req,
            &remove_lock_res, nullptr);
    ASSERT_EQ(remove_lock_res.status().code(), MetaServiceCode::OK);
    // get delete bitmap
    GetDeleteBitmapRequest get_delete_bitmap_req;
    GetDeleteBitmapResponse get_delete_bitmap_res;
    get_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
    get_delete_bitmap_req.set_tablet_id(tablet_id);
    std::vector<std::string> rowset_vector = {"r2", "r3", "r4"};
    for (const auto& rowset : rowset_vector) {
        get_delete_bitmap_req.add_rowset_ids(rowset);
        get_delete_bitmap_req.add_begin_versions(0);
        get_delete_bitmap_req.add_end_versions(6);
    }
    meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                    &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
    ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
    auto size = rowset_segment_version_vector.size();
    ASSERT_EQ(get_delete_bitmap_res.rowset_ids_size(), size);
    ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), size);
    ASSERT_EQ(get_delete_bitmap_res.versions_size(), size);
    ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), size);
    // update pre rowset delete bitmap
    update_delete_bitmap_req.clear_rowset_ids();
    update_delete_bitmap_req.clear_segment_ids();
    update_delete_bitmap_req.clear_versions();
    update_delete_bitmap_req.clear_segment_delete_bitmaps();
    update_delete_bitmap_req.set_lock_id(-3);
    update_delete_bitmap_req.set_without_lock(true);
    update_delete_bitmap_req.set_initiator(tablet_id);
    update_delete_bitmap_req.set_pre_rowset_agg_start_version(4);
    update_delete_bitmap_req.set_pre_rowset_agg_end_version(6);
    std::vector<std::tuple<std::string, int64_t, int64_t, int64_t>>
            new_rowset_segment_version_vector = {/* r2-0 */ {"r2", 0, 6, 2},
                                                 /* r3-0 */ {"r3", 0, 6, 3},
                                                 /* r3-1 */ {"r3", 1, 6, 3},
                                                 /* r3-2 */ {"r3", 2, 6, 3},
                                                 /* r4-0 */ {"r4", 0, 6, 4}};
    std::string new_large_value = generate_random_string(300 * 1000);
    for (const auto& [rowset, segment, version, rowset_version] :
         new_rowset_segment_version_vector) {
        update_delete_bitmap_req.add_rowset_ids(rowset);
        update_delete_bitmap_req.add_segment_ids(segment);
        update_delete_bitmap_req.add_versions(version);
        update_delete_bitmap_req.add_segment_delete_bitmaps(new_large_value);
        update_delete_bitmap_req.add_pre_rowset_versions(rowset_version);
    }
    meta_service->update_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                       &update_delete_bitmap_req, &update_delete_bitmap_res,
                                       nullptr);
    ASSERT_EQ(update_delete_bitmap_res.status().code(),
              inject ? MetaServiceCode::KV_TXN_CONFLICT : MetaServiceCode::OK);
    // get delete bitmap again
    meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                    &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
    ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
    size = 6;
    if (inject) {
        size = 13;
    } else if (rowset_non_exist) {
        size = 12;
    }
    ASSERT_EQ(get_delete_bitmap_res.rowset_ids_size(), size);
    ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), size);
    ASSERT_EQ(get_delete_bitmap_res.versions_size(), size);
    ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), size);
    std::vector<std::tuple<std::string, int64_t, int64_t, std::string>> expected_dm;
    if (inject) {
        expected_dm = {/* r2-0 */ {"r2", 0, 3, large_value},
                       {"r2", 0, 4, large_value},
                       {"r2", 0, 5, large_value},
                       {"r2", 0, 6, new_large_value},
                       /* r3-0 is agg */ {"r3", 0, 4, large_value},
                       {"r3", 1, 4, large_value},
                       {"r3", 2, 4, large_value},
                       {"r3", 0, 5, large_value},
                       {"r3", 1, 5, large_value},
                       {"r3", 0, 6, new_large_value},
                       {"r3", 2, 6, large_value},
                       /* r4-0 */ {"r4", 0, 5, large_value},
                       {"r4", 0, 6, large_value}};
    } else if (rowset_non_exist) {
        expected_dm = {/* r2-0 */ {"r2", 0, 3, large_value},
                       {"r2", 0, 4, large_value},
                       {"r2", 0, 5, large_value},
                       {"r2", 0, 6, large_value},
                       /* r3-0 */ {"r3", 0, 4, large_value},
                       {"r3", 1, 4, large_value},
                       {"r3", 2, 4, large_value},
                       {"r3", 0, 5, large_value},
                       {"r3", 1, 5, large_value},
                       {"r3", 0, 6, large_value},
                       {"r3", 2, 6, large_value},
                       /* r4-0 */ {"r4", 0, 6, new_large_value}};
    } else {
        expected_dm = {/* r2-0 */ {"r2", 0, 3, large_value},
                       {"r2", 0, 6, new_large_value},
                       /* r3-0 */ {"r3", 0, 6, new_large_value},
                       /* r3-1 */ {"r3", 1, 6, new_large_value},
                       /* r3-2 */ {"r3", 2, 6, new_large_value},
                       /* r4-0 */ {"r4", 0, 6, new_large_value}};
    }
    for (size_t i = 0; i < get_delete_bitmap_res.rowset_ids_size(); i++) {
        ASSERT_EQ(get_delete_bitmap_res.rowset_ids(i), std::get<0>(expected_dm[i]));
        ASSERT_EQ(get_delete_bitmap_res.segment_ids(i), std::get<1>(expected_dm[i]));
        ASSERT_EQ(get_delete_bitmap_res.versions(i), std::get<2>(expected_dm[i]));
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(i), std::get<3>(expected_dm[i]));
    }
}

TEST(MetaServiceTest, UpdateDeleteBitmapWithRemovePreDeleteBitmap) {
    auto meta_service = get_meta_service();
    [[maybe_unused]] auto sp = SyncPoint::get_instance();
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
    };

    update_delete_bitmap_with_remove_pre(meta_service.get(), 200, 202);

    int64_t max_txn_commit_byte = config::max_txn_commit_byte;
    config::max_txn_commit_byte = 1000;
    update_delete_bitmap_with_remove_pre(meta_service.get(), 300, 302);

    sp->set_call_back("update_delete_bitmap:commit:err", [&](auto&& args) {
        auto initiator = try_any_cast<int64_t>(args[0]);
        auto i = try_any_cast<size_t>(args[1]);
        if (initiator == 402 && i == 2) {
            *try_any_cast<TxnErrorCode*>(args[2]) = TxnErrorCode::TXN_CONFLICT;
        }
    });
    sp->enable_processing();
    update_delete_bitmap_with_remove_pre(meta_service.get(), 400, 402, true);
    sp->clear_all_call_backs();
    sp->clear_trace();
    sp->disable_processing();
    config::max_txn_commit_byte = max_txn_commit_byte;

    update_delete_bitmap_with_remove_pre(meta_service.get(), 500, 502, false, true);
}

TEST(MetaServiceTest, GetDeleteBitmapWithIdx) {
    auto meta_service = get_meta_service();
    extern std::string get_instance_id(const std::shared_ptr<ResourceManager>& rc_mgr,
                                       const std::string& cloud_unique_id);
    auto instance_id = get_instance_id(meta_service->resource_mgr(), "test_cloud_unique_id");
    int64_t db_id = 1;
    int64_t table_id = 1;
    int64_t index_id = 1;
    int64_t partition_id = 1;
    int64_t tablet_id = 123;

    brpc::Controller cntl;
    GetDeleteBitmapRequest req;
    GetDeleteBitmapResponse res;
    req.set_cloud_unique_id("test_cloud_unique_id");
    req.set_tablet_id(tablet_id);
    TabletIndexPB idx;
    idx.set_tablet_id(tablet_id);
    idx.set_index_id(index_id);
    idx.set_db_id(db_id);
    idx.set_partition_id(partition_id);
    idx.set_table_id(table_id);
    *(req.mutable_idx()) = idx;
    req.set_base_compaction_cnt(9);
    req.set_cumulative_compaction_cnt(19);
    req.set_cumulative_point(21);

    meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl), &req,
                                    &res, nullptr);
    EXPECT_EQ(res.status().code(), MetaServiceCode::TABLET_NOT_FOUND);

    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    std::string stats_key =
            stats_tablet_key({instance_id, table_id, index_id, partition_id, tablet_id});
    TabletStatsPB stats;
    stats.set_base_compaction_cnt(9);
    stats.set_cumulative_compaction_cnt(19);
    stats.set_cumulative_point(20);
    txn->put(stats_key, stats.SerializeAsString());
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl), &req,
                                    &res, nullptr);
    EXPECT_EQ(res.status().code(), MetaServiceCode::ROWSETS_EXPIRED);

    req.set_cumulative_point(20);
    meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl), &req,
                                    &res, nullptr);
    EXPECT_EQ(res.status().code(), MetaServiceCode::OK);

    req.add_rowset_ids("1234");
    req.add_begin_versions(1);
    req.add_end_versions(2);
    req.add_end_versions(3);
    meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl), &req,
                                    &res, nullptr);
    EXPECT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
}

TEST(MetaServiceTest, DeleteBimapCommitTxnTest) {
    auto meta_service = get_meta_service();
    extern std::string get_instance_id(const std::shared_ptr<ResourceManager>& rc_mgr,
                                       const std::string& cloud_unique_id);
    auto instance_id = get_instance_id(meta_service->resource_mgr(), "test_cloud_unique_id");

    // case: first version of rowset
    {
        int64_t txn_id = 98765;
        int64_t table_id = 123456; // same as table_id of tmp rowset
        int64_t db_id = 222;
        int64_t tablet_id_base = 8113;
        int64_t partition_id = 1234;
        // begin txn
        {
            brpc::Controller cntl;
            BeginTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            TxnInfoPB txn_info_pb;
            txn_info_pb.set_db_id(db_id);
            txn_info_pb.set_label("test_label");
            txn_info_pb.add_table_ids(table_id);
            txn_info_pb.set_timeout_ms(36000);
            req.mutable_txn_info()->CopyFrom(txn_info_pb);
            BeginTxnResponse res;
            meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                    &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            txn_id = res.txn_id();
        }

        // mock rowset and tablet
        for (int i = 0; i < 5; ++i) {
            create_tablet(meta_service.get(), table_id, 1235, partition_id, tablet_id_base + i);
            auto tmp_rowset = create_rowset(txn_id, tablet_id_base + i);
            tmp_rowset.set_partition_id(partition_id);
            CreateRowsetResponse res;
            commit_rowset(meta_service.get(), tmp_rowset, res);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }

        // update delete bitmap
        {
            // get delete bitmap update lock
            brpc::Controller cntl;
            GetDeleteBitmapUpdateLockRequest get_lock_req;
            GetDeleteBitmapUpdateLockResponse get_lock_res;
            get_lock_req.set_cloud_unique_id("test_cloud_unique_id");
            get_lock_req.set_table_id(table_id);
            get_lock_req.add_partition_ids(partition_id);
            get_lock_req.set_expiration(5);
            get_lock_req.set_lock_id(txn_id);
            get_lock_req.set_initiator(-1);
            meta_service->get_delete_bitmap_update_lock(
                    reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &get_lock_req,
                    &get_lock_res, nullptr);
            ASSERT_EQ(get_lock_res.status().code(), MetaServiceCode::OK);

            // first update delete bitmap
            UpdateDeleteBitmapRequest update_delete_bitmap_req;
            UpdateDeleteBitmapResponse update_delete_bitmap_res;
            update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
            update_delete_bitmap_req.set_table_id(table_id);
            update_delete_bitmap_req.set_partition_id(partition_id);
            update_delete_bitmap_req.set_lock_id(txn_id);
            update_delete_bitmap_req.set_initiator(-1);
            update_delete_bitmap_req.set_tablet_id(tablet_id_base);

            update_delete_bitmap_req.add_rowset_ids("123");
            update_delete_bitmap_req.add_segment_ids(1);
            update_delete_bitmap_req.add_versions(2);
            update_delete_bitmap_req.add_segment_delete_bitmaps("abc0");

            meta_service->update_delete_bitmap(
                    reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                    &update_delete_bitmap_req, &update_delete_bitmap_res, nullptr);
            ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);
        }

        // check delete bitmap update lock and pending delete bitmap
        {
            std::unique_ptr<Transaction> txn;
            ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
            std::string lock_key = meta_delete_bitmap_update_lock_key({instance_id, table_id, -1});
            std::string lock_val;
            auto ret = txn->get(lock_key, &lock_val);
            ASSERT_EQ(ret, TxnErrorCode::TXN_OK);
            DeleteBitmapUpdateLockPB lock_info;
            ASSERT_TRUE(lock_info.ParseFromString(lock_val));

            std::string pending_key = meta_pending_delete_bitmap_key({instance_id, tablet_id_base});
            std::string pending_val;
            ret = txn->get(pending_key, &pending_val);
            ASSERT_EQ(ret, TxnErrorCode::TXN_OK);
            PendingDeleteBitmapPB pending_info;
            ASSERT_TRUE(pending_info.ParseFromString(pending_val));
        }

        // commit txn
        {
            brpc::Controller cntl;
            CommitTxnRequest req;
            req.set_cloud_unique_id("test_cloud_unique_id");
            req.set_db_id(db_id);
            req.set_txn_id(txn_id);
            req.add_mow_table_ids(table_id);
            CommitTxnResponse res;
            meta_service->commit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        }

        // check delete bitmap update lock and pending delete bitmap
        {
            std::unique_ptr<Transaction> txn;
            ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
            std::string lock_key = meta_delete_bitmap_update_lock_key({instance_id, table_id, -1});
            std::string lock_val;
            auto ret = txn->get(lock_key, &lock_val);
            ASSERT_EQ(ret, TxnErrorCode::TXN_KEY_NOT_FOUND);

            std::string pending_key = meta_pending_delete_bitmap_key({instance_id, tablet_id_base});
            std::string pending_val;
            ret = txn->get(pending_key, &pending_val);
            ASSERT_EQ(ret, TxnErrorCode::TXN_KEY_NOT_FOUND);
        }
    }
}

TEST(MetaServiceTest, GetDeleteBitmapWithRetryTest1) {
    auto meta_service = get_meta_service();
    SyncPoint::get_instance()->enable_processing();
    size_t index = 0;
    SyncPoint::get_instance()->set_call_back("get_delete_bitmap_code", [&](auto&& args) {
        LOG(INFO) << "GET_DELETE_BITMAP_CODE,index=" << index;
        if (++index < 2) {
            *doris::try_any_cast<MetaServiceCode*>(args[0]) = MetaServiceCode::KV_TXN_TOO_OLD;
        }
    });

    // get delete bitmap update lock
    brpc::Controller cntl;
    GetDeleteBitmapUpdateLockRequest get_lock_req;
    GetDeleteBitmapUpdateLockResponse get_lock_res;
    get_lock_req.set_cloud_unique_id("test_cloud_unique_id");
    get_lock_req.set_table_id(100);
    get_lock_req.add_partition_ids(123);
    get_lock_req.set_expiration(5);
    get_lock_req.set_lock_id(888);
    get_lock_req.set_initiator(-1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &get_lock_req,
            &get_lock_res, nullptr);
    ASSERT_EQ(get_lock_res.status().code(), MetaServiceCode::OK);

    //first update new key
    UpdateDeleteBitmapRequest update_delete_bitmap_req;
    UpdateDeleteBitmapResponse update_delete_bitmap_res;
    update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
    update_delete_bitmap_req.set_table_id(100);
    update_delete_bitmap_req.set_partition_id(123);
    update_delete_bitmap_req.set_lock_id(888);
    update_delete_bitmap_req.set_initiator(-1);
    update_delete_bitmap_req.set_tablet_id(333);
    std::string large_value = generate_random_string(300 * 1000 * 3);
    update_delete_bitmap_req.add_rowset_ids("456");
    update_delete_bitmap_req.add_segment_ids(0);
    update_delete_bitmap_req.add_versions(2);
    update_delete_bitmap_req.add_segment_delete_bitmaps(large_value);
    meta_service->update_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                       &update_delete_bitmap_req, &update_delete_bitmap_res,
                                       nullptr);
    ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);

    GetDeleteBitmapRequest get_delete_bitmap_req;
    GetDeleteBitmapResponse get_delete_bitmap_res;
    get_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
    get_delete_bitmap_req.set_tablet_id(333);

    get_delete_bitmap_req.add_rowset_ids("456");
    get_delete_bitmap_req.add_begin_versions(2);
    get_delete_bitmap_req.add_end_versions(2);

    meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                    &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
    ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(get_delete_bitmap_res.rowset_ids_size(), 1);
    ASSERT_EQ(get_delete_bitmap_res.segment_ids_size(), 1);
    ASSERT_EQ(get_delete_bitmap_res.versions_size(), 1);
    ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), 1);

    ASSERT_EQ(get_delete_bitmap_res.rowset_ids(0), "456");
    ASSERT_EQ(get_delete_bitmap_res.segment_ids(0), 0);
    ASSERT_EQ(get_delete_bitmap_res.versions(0), 2);
    ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(0), large_value);

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, GetDeleteBitmapWithRetryTest2) {
    auto meta_service = get_meta_service();
    SyncPoint::get_instance()->enable_processing();
    size_t index = 0;
    int store_version = 1;
    SyncPoint::get_instance()->set_call_back("get_delete_bitmap_test", [&](auto&& args) {
        auto* test = try_any_cast<bool*>(args[0]);
        *test = true;
        LOG(INFO) << "GET_DELETE_BITMAP_TEST, test=" << *test;
    });
    SyncPoint::get_instance()->set_call_back("get_delete_bitmap_err", [&](auto&& args) {
        auto* round = try_any_cast<int64_t*>(args[0]);
        LOG(INFO) << "GET_DELETE_BITMAP_CODE,index=" << index << ",round=" << *round;
        if (*round > 2 && ++index < 2) {
            *try_any_cast<TxnErrorCode*>(args[1]) = TxnErrorCode::TXN_TOO_OLD;
        }
        if (store_version == 2 && *round < 3) {
            *try_any_cast<TxnErrorCode*>(args[1]) = TxnErrorCode::TXN_TOO_OLD;
        }
    });

    // get delete bitmap update lock
    brpc::Controller cntl;
    GetDeleteBitmapUpdateLockRequest get_lock_req;
    GetDeleteBitmapUpdateLockResponse get_lock_res;
    get_lock_req.set_cloud_unique_id("test_cloud_unique_id");
    get_lock_req.set_table_id(100);
    get_lock_req.add_partition_ids(123);
    get_lock_req.set_expiration(5);
    get_lock_req.set_lock_id(888);
    get_lock_req.set_initiator(-1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &get_lock_req,
            &get_lock_res, nullptr);
    ASSERT_EQ(get_lock_res.status().code(), MetaServiceCode::OK);

    //first update new key
    UpdateDeleteBitmapRequest update_delete_bitmap_req;
    UpdateDeleteBitmapResponse update_delete_bitmap_res;
    update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
    update_delete_bitmap_req.set_table_id(100);
    update_delete_bitmap_req.set_partition_id(123);
    update_delete_bitmap_req.set_lock_id(888);
    update_delete_bitmap_req.set_initiator(-1);
    update_delete_bitmap_req.set_tablet_id(333);
    std::string rowset_id = "456";
    std::string segment_delete_bitmaps[5];
    for (int i = 0; i < 5; i++) {
        segment_delete_bitmaps[i] = generate_random_string(300 * 1000 * 3);
    }
    int count = 5;
    DeleteBitmapPB delete_bitmap;
    for (int i = 0; i < count; i++) {
        update_delete_bitmap_req.add_rowset_ids(rowset_id);
        update_delete_bitmap_req.add_segment_ids(i);
        update_delete_bitmap_req.add_versions(i + 1);
        update_delete_bitmap_req.add_segment_delete_bitmaps(segment_delete_bitmaps[i]);

        delete_bitmap.add_rowset_ids(rowset_id);
        delete_bitmap.add_segment_ids(i);
        delete_bitmap.add_versions(i + 1);
        delete_bitmap.add_segment_delete_bitmaps(segment_delete_bitmaps[i]);
    }
    // v2 update delete bitmap
    DeleteBitmapStoragePB delete_bitmap_storage_pb;
    delete_bitmap_storage_pb.set_store_in_fdb(true);
    *(delete_bitmap_storage_pb.mutable_delete_bitmap()) = std::move(delete_bitmap);
    *(update_delete_bitmap_req.add_delete_bitmap_storages()) = std::move(delete_bitmap_storage_pb);
    update_delete_bitmap_req.add_delta_rowset_ids(rowset_id);
    meta_service->update_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                       &update_delete_bitmap_req, &update_delete_bitmap_res,
                                       nullptr);
    ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);

    // v1 get delete bitmap
    GetDeleteBitmapRequest get_delete_bitmap_req;
    get_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
    get_delete_bitmap_req.set_tablet_id(333);

    // add a rowset which does not exist
    get_delete_bitmap_req.add_rowset_ids(rowset_id + "_non");
    get_delete_bitmap_req.add_begin_versions(0);
    get_delete_bitmap_req.add_end_versions(count);

    get_delete_bitmap_req.add_rowset_ids(rowset_id);
    get_delete_bitmap_req.add_begin_versions(0);
    get_delete_bitmap_req.add_end_versions(count);
    {
        GetDeleteBitmapResponse get_delete_bitmap_res;
        meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                        &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
        ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(get_delete_bitmap_res.rowset_ids_size(), count);
        ASSERT_EQ(get_delete_bitmap_res.segment_ids_size(), count);
        ASSERT_EQ(get_delete_bitmap_res.versions_size(), count);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), count);

        for (int i = 0; i < count; i++) {
            ASSERT_EQ(get_delete_bitmap_res.rowset_ids(i), rowset_id);
            ASSERT_EQ(get_delete_bitmap_res.segment_ids(i), i);
            ASSERT_EQ(get_delete_bitmap_res.versions(i), i + 1);
            ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(i), segment_delete_bitmaps[i]);
        }
    }

    // v2 get delete bitmap
    {
        store_version = 2;
        get_delete_bitmap_req.set_store_version(2);
        GetDeleteBitmapResponse get_delete_bitmap_res;
        meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                        &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
        ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(get_delete_bitmap_res.delete_bitmap_storages_size(), 1);
        auto& delete_bitmap_storage = get_delete_bitmap_res.delete_bitmap_storages(0);
        ASSERT_TRUE(delete_bitmap_storage.store_in_fdb());
        auto& delete_bitmap_pb = delete_bitmap_storage.delete_bitmap();
        ASSERT_EQ(delete_bitmap_pb.rowset_ids_size(), count);
        ASSERT_EQ(delete_bitmap_pb.segment_ids_size(), count);
        ASSERT_EQ(delete_bitmap_pb.versions_size(), count);
        ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps_size(), count);

        for (int i = 0; i < count; i++) {
            ASSERT_EQ(delete_bitmap_pb.rowset_ids(i), rowset_id);
            ASSERT_EQ(delete_bitmap_pb.segment_ids(i), i);
            ASSERT_EQ(delete_bitmap_pb.versions(i), i + 1);
            ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps(i), segment_delete_bitmaps[i]);
        }
    }

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, GetDeleteBitmapWithRetryTest3) {
    auto meta_service = get_meta_service();
    SyncPoint::get_instance()->enable_processing();
    size_t index = 0;
    SyncPoint::get_instance()->set_call_back("get_delete_bitmap_err", [&](auto&& args) {
        auto* round = try_any_cast<int64_t*>(args[0]);
        LOG(INFO) << "GET_DELETE_BITMAP_CODE,index=" << index << ",round=" << *round;
        if (*round > 2 && ++index < 2) {
            *try_any_cast<TxnErrorCode*>(args[1]) = TxnErrorCode::TXN_TOO_OLD;
        }
    });

    // get delete bitmap update lock
    brpc::Controller cntl;
    GetDeleteBitmapUpdateLockRequest get_lock_req;
    GetDeleteBitmapUpdateLockResponse get_lock_res;
    get_lock_req.set_cloud_unique_id("test_cloud_unique_id");
    get_lock_req.set_table_id(100);
    get_lock_req.add_partition_ids(123);
    get_lock_req.set_expiration(5);
    get_lock_req.set_lock_id(888);
    get_lock_req.set_initiator(-1);
    meta_service->get_delete_bitmap_update_lock(
            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &get_lock_req,
            &get_lock_res, nullptr);
    ASSERT_EQ(get_lock_res.status().code(), MetaServiceCode::OK);

    //first update new key
    UpdateDeleteBitmapRequest update_delete_bitmap_req;
    UpdateDeleteBitmapResponse update_delete_bitmap_res;
    update_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
    update_delete_bitmap_req.set_table_id(100);
    update_delete_bitmap_req.set_partition_id(123);
    update_delete_bitmap_req.set_lock_id(888);
    update_delete_bitmap_req.set_initiator(-1);
    update_delete_bitmap_req.set_tablet_id(333);
    std::string rowset_id = "456";
    std::string segment_delete_bitmaps[5];
    for (int i = 0; i < 5; i++) {
        segment_delete_bitmaps[i] = generate_random_string(300 * 1000 * 3);
    }
    int count = 5;
    for (int i = 0; i < count; i++) {
        update_delete_bitmap_req.add_rowset_ids(rowset_id);
        update_delete_bitmap_req.add_segment_ids(i);
        update_delete_bitmap_req.add_versions(i + 1);
        update_delete_bitmap_req.add_segment_delete_bitmaps(segment_delete_bitmaps[i]);
    }
    meta_service->update_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                       &update_delete_bitmap_req, &update_delete_bitmap_res,
                                       nullptr);
    ASSERT_EQ(update_delete_bitmap_res.status().code(), MetaServiceCode::OK);

    GetDeleteBitmapRequest get_delete_bitmap_req;
    GetDeleteBitmapResponse get_delete_bitmap_res;
    get_delete_bitmap_req.set_cloud_unique_id("test_cloud_unique_id");
    get_delete_bitmap_req.set_tablet_id(333);

    get_delete_bitmap_req.add_rowset_ids(rowset_id);
    get_delete_bitmap_req.add_begin_versions(1);
    get_delete_bitmap_req.add_end_versions(count);

    meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
                                    &get_delete_bitmap_req, &get_delete_bitmap_res, nullptr);
    ASSERT_EQ(get_delete_bitmap_res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(get_delete_bitmap_res.rowset_ids_size(), count);
    ASSERT_EQ(get_delete_bitmap_res.segment_ids_size(), count);
    ASSERT_EQ(get_delete_bitmap_res.versions_size(), count);
    ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps_size(), count);

    for (int i = 0; i < count; i++) {
        ASSERT_EQ(get_delete_bitmap_res.rowset_ids(i), rowset_id);
        ASSERT_EQ(get_delete_bitmap_res.segment_ids(i), i);
        ASSERT_EQ(get_delete_bitmap_res.versions(i), i + 1);
        ASSERT_EQ(get_delete_bitmap_res.segment_delete_bitmaps(i), segment_delete_bitmaps[i]);
    }
    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, GetVersion) {
    auto service = get_meta_service();

    int64_t table_id = 1;
    int64_t partition_id = 1;
    int64_t tablet_id = 1;

    // INVALID_ARGUMENT
    {
        brpc::Controller ctrl;
        GetVersionRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_table_id(table_id);
        req.set_partition_id(partition_id);

        GetVersionResponse resp;
        service->get_version(&ctrl, &req, &resp, nullptr);

        ASSERT_EQ(resp.status().code(), MetaServiceCode::INVALID_ARGUMENT)
                << " status is " << resp.status().DebugString();
    }

    {
        brpc::Controller ctrl;
        GetVersionRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_db_id(1);
        req.set_table_id(table_id);
        req.set_partition_id(partition_id);

        GetVersionResponse resp;
        service->get_version(&ctrl, &req, &resp, nullptr);

        ASSERT_EQ(resp.status().code(), MetaServiceCode::VERSION_NOT_FOUND)
                << " status is " << resp.status().DebugString();
    }

    create_tablet(service.get(), table_id, 1, partition_id, tablet_id);
    insert_rowset(service.get(), 1, "get_version_label_1", table_id, partition_id, tablet_id);

    {
        brpc::Controller ctrl;
        GetVersionRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_db_id(1);
        req.set_table_id(table_id);
        req.set_partition_id(partition_id);

        GetVersionResponse resp;
        service->get_version(&ctrl, &req, &resp, nullptr);

        ASSERT_EQ(resp.status().code(), MetaServiceCode::OK)
                << " status is " << resp.status().DebugString();
        ASSERT_EQ(resp.version(), 2);
    }
}

TEST(MetaServiceTest, BatchGetVersion) {
    struct TestCase {
        std::vector<int64_t> table_ids;
        std::vector<int64_t> partition_ids;
        std::vector<int64_t> expected_versions;
        std::vector<
                std::tuple<int64_t /*table_id*/, int64_t /*partition_id*/, int64_t /*tablet_id*/>>
                insert_rowsets;
    };

    // table ids: 2, 3, 4, 5
    // partition ids: 6, 7, 8, 9
    std::vector<TestCase> cases = {
            // all version are missing
            {{1, 2, 3, 4}, {6, 7, 8, 9}, {-1, -1, -1, -1}, {}},
            // update table 1, partition 6
            {{1, 2, 3, 4}, {6, 7, 8, 9}, {2, -1, -1, -1}, {{1, 6, 1}}},
            // update table 2, partition 6
            // update table 3, partition 7
            {{1, 2, 3, 4}, {6, 7, 8, 9}, {2, -1, 2, 2}, {{3, 8, 3}, {4, 9, 4}}},
            // update table 1, partition 7 twice
            {{1, 2, 3, 4}, {6, 7, 8, 9}, {2, 3, 2, 2}, {{2, 7, 2}, {2, 7, 2}}},
    };

    auto service = get_meta_service();
    create_tablet(service.get(), 1, 1, 6, 1);
    create_tablet(service.get(), 2, 1, 7, 2);
    create_tablet(service.get(), 3, 1, 8, 3);
    create_tablet(service.get(), 4, 1, 9, 4);

    size_t num_cases = cases.size();
    size_t label_index = 0;
    for (size_t i = 0; i < num_cases; ++i) {
        auto& [table_ids, partition_ids, expected_versions, insert_rowsets] = cases[i];
        for (auto [table_id, partition_id, tablet_id] : insert_rowsets) {
            LOG(INFO) << "insert rowset for table " << table_id << " partition " << partition_id
                      << " tablet_id " << tablet_id;
            insert_rowset(service.get(), 1, std::to_string(++label_index), table_id, partition_id,
                          tablet_id);
        }

        brpc::Controller ctrl;
        GetVersionRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_db_id(-1);
        req.set_table_id(-1);
        req.set_partition_id(-1);
        req.set_batch_mode(true);
        for (size_t i = 0; i < table_ids.size(); ++i) req.add_db_ids(1);
        std::copy(table_ids.begin(), table_ids.end(),
                  google::protobuf::RepeatedFieldBackInserter(req.mutable_table_ids()));
        std::copy(partition_ids.begin(), partition_ids.end(),
                  google::protobuf::RepeatedFieldBackInserter(req.mutable_partition_ids()));

        GetVersionResponse resp;
        service->get_version(&ctrl, &req, &resp, nullptr);

        ASSERT_EQ(resp.status().code(), MetaServiceCode::OK)
                << "case " << i << " status is " << resp.status().msg()
                << ", code=" << resp.status().code();

        std::vector<int64_t> versions(resp.versions().begin(), resp.versions().end());
        EXPECT_EQ(versions, expected_versions) << "case " << i;
    }

    // INVALID_ARGUMENT
    {
        brpc::Controller ctrl;
        GetVersionRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_batch_mode(true);
        GetVersionResponse resp;
        service->get_version(&ctrl, &req, &resp, nullptr);
        ASSERT_EQ(resp.status().code(), MetaServiceCode::INVALID_ARGUMENT)
                << " status is " << resp.status().msg() << ", code=" << resp.status().code();
    }
}

TEST(MetaServiceTest, BatchGetVersionFallback) {
    constexpr size_t N = 100;
    size_t i = 0;
    auto sp = SyncPoint::get_instance();
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
    };
    sp->set_call_back("batch_get_version_err", [&](auto&& args) {
        if (i++ == N / 10) {
            *try_any_cast<TxnErrorCode*>(args) = TxnErrorCode::TXN_TOO_OLD;
        }
    });

    sp->enable_processing();

    auto service = get_meta_service();
    for (int64_t i = 1; i <= N; ++i) {
        create_tablet(service.get(), 1, 1, i, i);
        insert_rowset(service.get(), 1, std::to_string(i), 1, i, i);
    }

    brpc::Controller ctrl;
    GetVersionRequest req;
    req.set_cloud_unique_id("test_cloud_unique_id");
    req.set_db_id(-1);
    req.set_table_id(-1);
    req.set_partition_id(-1);
    req.set_batch_mode(true);
    for (size_t i = 1; i <= N; ++i) {
        req.add_db_ids(1);
        req.add_table_ids(1);
        req.add_partition_ids(i);
    }

    GetVersionResponse resp;
    service->get_version(&ctrl, &req, &resp, nullptr);

    ASSERT_EQ(resp.status().code(), MetaServiceCode::OK)
            << "case " << i << " status is " << resp.status().msg()
            << ", code=" << resp.status().code();

    ASSERT_EQ(resp.versions_size(), N);
}

extern bool is_dropped_tablet(Transaction* txn, const std::string& instance_id, int64_t index_id,
                              int64_t partition_id);

TEST(MetaServiceTest, IsDroppedTablet) {
    auto meta_service = get_meta_service();
    std::string instance_id = "IsDroppedTablet";
    auto sp = SyncPoint::get_instance();
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
    };
    sp->set_call_back("get_instance_id", [&](auto&& args) {
        auto* ret = try_any_cast_ret<std::string>(args);
        ret->first = instance_id;
        ret->second = true;
    });
    sp->enable_processing();

    meta_service = get_meta_service();
    auto reset_meta_service = [&meta_service] { meta_service = get_meta_service(); };

    constexpr int64_t index_id = 10002;
    constexpr int64_t partition_id = 10003;

    std::unique_ptr<Transaction> txn;
    RecycleIndexPB index_pb;
    auto index_key = recycle_index_key({instance_id, index_id});
    RecyclePartitionPB partition_pb;
    auto partition_key = recycle_partition_key({instance_id, partition_id});
    std::string val;
    // No recycle index and partition kv
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    EXPECT_FALSE(is_dropped_tablet(txn.get(), instance_id, index_id, partition_id));
    // Tablet in PREPARED index
    index_pb.set_state(RecycleIndexPB::PREPARED);
    val = index_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(index_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    EXPECT_FALSE(is_dropped_tablet(txn.get(), instance_id, index_id, partition_id));
    // Tablet in DROPPED/RECYCLING index
    reset_meta_service();
    index_pb.set_state(RecycleIndexPB::DROPPED);
    val = index_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(index_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    EXPECT_TRUE(is_dropped_tablet(txn.get(), instance_id, index_id, partition_id));
    reset_meta_service();
    index_pb.set_state(RecycleIndexPB::RECYCLING);
    val = index_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(index_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    EXPECT_TRUE(is_dropped_tablet(txn.get(), instance_id, index_id, partition_id));
    // Tablet in PREPARED partition
    reset_meta_service();
    partition_pb.set_state(RecyclePartitionPB::PREPARED);
    val = partition_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(partition_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    EXPECT_FALSE(is_dropped_tablet(txn.get(), instance_id, index_id, partition_id));
    // Tablet in DROPPED/RECYCLING partition
    reset_meta_service();
    partition_pb.set_state(RecyclePartitionPB::DROPPED);
    val = partition_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(partition_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    EXPECT_TRUE(is_dropped_tablet(txn.get(), instance_id, index_id, partition_id));
    reset_meta_service();
    partition_pb.set_state(RecyclePartitionPB::RECYCLING);
    val = partition_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(partition_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    EXPECT_TRUE(is_dropped_tablet(txn.get(), instance_id, index_id, partition_id));
}

TEST(MetaServiceTest, IndexRequest) {
    auto meta_service = get_meta_service();
    std::string instance_id = "IndexRequest";
    auto sp = SyncPoint::get_instance();
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
    };
    sp->set_call_back("get_instance_id", [&](auto&& args) {
        auto* ret = try_any_cast_ret<std::string>(args);
        ret->first = instance_id;
        ret->second = true;
    });
    sp->enable_processing();

    auto reset_meta_service = [&meta_service] { meta_service = get_meta_service(); };
    constexpr int64_t table_id = 10001;
    constexpr int64_t index_id = 10002;
    constexpr int64_t partition_id = 10003;
    constexpr int64_t tablet_id = 10004;

    std::unique_ptr<Transaction> txn;
    doris::TabletMetaCloudPB tablet_pb;
    tablet_pb.set_table_id(table_id);
    tablet_pb.set_index_id(index_id);
    tablet_pb.set_partition_id(partition_id);
    tablet_pb.set_tablet_id(tablet_id);
    auto tablet_key = meta_tablet_key({instance_id, table_id, index_id, partition_id, tablet_id});
    auto tablet_val = tablet_pb.SerializeAsString();
    RecycleIndexPB index_pb;
    auto index_key = recycle_index_key({instance_id, index_id});
    int64_t val_int = 0;
    auto tbl_version_key = table_version_key({instance_id, 1, table_id});
    std::string val;

    // ------------Test prepare index------------
    brpc::Controller ctrl;
    IndexRequest req;
    IndexResponse res;
    meta_service->prepare_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    req.set_db_id(1);
    req.set_table_id(table_id);
    req.add_index_ids(index_id);
    req.set_is_new_table(true);
    // Last state UNKNOWN
    res.Clear();
    meta_service->prepare_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(index_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(index_pb.ParseFromString(val));
    ASSERT_EQ(index_pb.state(), RecycleIndexPB::PREPARED);
    // Last state PREPARED
    res.Clear();
    meta_service->prepare_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(index_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(index_pb.ParseFromString(val));
    ASSERT_EQ(index_pb.state(), RecycleIndexPB::PREPARED);
    // Last state DROPPED
    reset_meta_service();
    index_pb.set_state(RecycleIndexPB::DROPPED);
    val = index_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(index_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->prepare_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(index_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(index_pb.ParseFromString(val));
    ASSERT_EQ(index_pb.state(), RecycleIndexPB::DROPPED);
    // Last state RECYCLING
    reset_meta_service();
    index_pb.set_state(RecycleIndexPB::RECYCLING);
    val = index_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(index_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->prepare_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(index_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(index_pb.ParseFromString(val));
    ASSERT_EQ(index_pb.state(), RecycleIndexPB::RECYCLING);
    // Last state UNKNOWN but tablet meta existed
    reset_meta_service();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->remove(index_key);
    txn->put(tablet_key, tablet_val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->prepare_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::ALREADY_EXISTED);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(index_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    // Prepare index should not init table version
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    // ------------Test commit index------------
    reset_meta_service();
    req.Clear();
    meta_service->commit_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    req.set_db_id(1);
    req.set_table_id(table_id);
    req.add_index_ids(index_id);
    req.set_is_new_table(true);
    // Last state UNKNOWN
    res.Clear();
    meta_service->commit_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(index_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    // Last state PREPARED
    reset_meta_service();
    index_pb.set_state(RecycleIndexPB::PREPARED);
    val = index_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(index_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->commit_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(index_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    // First commit index should init table version
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_OK);
    val_int = *reinterpret_cast<const int64_t*>(val.data());
    ASSERT_EQ(val_int, 1);
    // Last state DROPPED
    reset_meta_service();
    index_pb.set_state(RecycleIndexPB::DROPPED);
    val = index_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(index_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->commit_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(index_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(index_pb.ParseFromString(val));
    ASSERT_EQ(index_pb.state(), RecycleIndexPB::DROPPED);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    // Last state RECYCLING
    reset_meta_service();
    index_pb.set_state(RecycleIndexPB::RECYCLING);
    val = index_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(index_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->commit_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(index_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(index_pb.ParseFromString(val));
    ASSERT_EQ(index_pb.state(), RecycleIndexPB::RECYCLING);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    // Last state UNKNOWN but tablet meta existed
    reset_meta_service();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(tablet_key, tablet_val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->commit_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(index_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    // ------------Test drop index------------
    reset_meta_service();
    req.Clear();
    meta_service->drop_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    req.set_db_id(1);
    req.set_table_id(table_id);
    req.add_index_ids(index_id);
    req.set_is_new_table(true);
    // Last state UNKNOWN
    res.Clear();
    meta_service->drop_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(index_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(index_pb.ParseFromString(val));
    ASSERT_EQ(index_pb.state(), RecycleIndexPB::DROPPED);
    // Last state PREPARED
    reset_meta_service();
    index_pb.set_state(RecycleIndexPB::PREPARED);
    val = index_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(index_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->drop_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(index_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(index_pb.ParseFromString(val));
    ASSERT_EQ(index_pb.state(), RecycleIndexPB::DROPPED);
    // Last state DROPPED
    reset_meta_service();
    index_pb.set_state(RecycleIndexPB::DROPPED);
    val = index_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(index_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->drop_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(index_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(index_pb.ParseFromString(val));
    ASSERT_EQ(index_pb.state(), RecycleIndexPB::DROPPED);
    // Last state RECYCLING
    reset_meta_service();
    index_pb.set_state(RecycleIndexPB::RECYCLING);
    val = index_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(index_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->drop_index(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(index_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(index_pb.ParseFromString(val));
    ASSERT_EQ(index_pb.state(), RecycleIndexPB::RECYCLING);
    // Drop index should not init table version
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
}

TEST(MetaServiceTest, PartitionRequest) {
    auto meta_service = get_meta_service();
    std::string instance_id = "PartitionRequest";
    auto sp = SyncPoint::get_instance();
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
    };
    sp->set_call_back("get_instance_id", [&](auto&& args) {
        auto* ret = try_any_cast_ret<std::string>(args);
        ret->first = instance_id;
        ret->second = true;
    });
    sp->enable_processing();

    auto reset_meta_service = [&meta_service] { meta_service = get_meta_service(); };
    constexpr int64_t table_id = 10001;
    constexpr int64_t index_id = 10002;
    constexpr int64_t partition_id = 10003;
    constexpr int64_t tablet_id = 10004;

    std::unique_ptr<Transaction> txn;
    doris::TabletMetaCloudPB tablet_pb;
    tablet_pb.set_table_id(table_id);
    tablet_pb.set_index_id(index_id);
    tablet_pb.set_partition_id(partition_id);
    tablet_pb.set_tablet_id(tablet_id);
    auto tablet_key = meta_tablet_key({instance_id, table_id, index_id, partition_id, tablet_id});
    auto tablet_val = tablet_pb.SerializeAsString();
    RecyclePartitionPB partition_pb;
    auto partition_key = recycle_partition_key({instance_id, partition_id});
    int64_t val_int = 0;
    auto tbl_version_key = table_version_key({instance_id, 1, table_id});
    std::string val;
    // ------------Test prepare partition------------
    brpc::Controller ctrl;
    PartitionRequest req;
    PartitionResponse res;
    meta_service->prepare_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    req.set_db_id(1);
    req.set_table_id(table_id);
    req.add_index_ids(index_id);
    req.add_partition_ids(partition_id);
    // Last state UNKNOWN
    res.Clear();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->atomic_add(tbl_version_key, 1);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    meta_service->prepare_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(partition_pb.ParseFromString(val));
    ASSERT_EQ(partition_pb.state(), RecyclePartitionPB::PREPARED);
    // Prepare partition should not update table version
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_OK);
    val_int = *reinterpret_cast<const int64_t*>(val.data());
    ASSERT_EQ(val_int, 1);
    // Last state PREPARED
    res.Clear();
    meta_service->prepare_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(partition_pb.ParseFromString(val));
    ASSERT_EQ(partition_pb.state(), RecyclePartitionPB::PREPARED);
    // Last state DROPPED
    reset_meta_service();
    partition_pb.set_state(RecyclePartitionPB::DROPPED);
    val = partition_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(partition_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->prepare_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(partition_pb.ParseFromString(val));
    ASSERT_EQ(partition_pb.state(), RecyclePartitionPB::DROPPED);
    // Last state RECYCLING
    reset_meta_service();
    partition_pb.set_state(RecyclePartitionPB::RECYCLING);
    val = partition_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(partition_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->prepare_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(partition_pb.ParseFromString(val));
    ASSERT_EQ(partition_pb.state(), RecyclePartitionPB::RECYCLING);
    // Last state UNKNOWN but tablet meta existed
    reset_meta_service();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->remove(partition_key);
    txn->put(tablet_key, tablet_val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->prepare_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::ALREADY_EXISTED);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    // ------------Test commit partition------------
    reset_meta_service();
    req.Clear();
    meta_service->commit_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    req.set_db_id(1);
    req.set_table_id(table_id);
    req.add_index_ids(index_id);
    req.add_partition_ids(partition_id);
    // Last state UNKNOWN
    res.Clear();
    meta_service->commit_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    // Last state PREPARED
    reset_meta_service();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->atomic_add(tbl_version_key, 1);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    partition_pb.set_state(RecyclePartitionPB::PREPARED);
    val = partition_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(partition_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->commit_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    // Commit partition should update table version
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_OK);
    val_int = *reinterpret_cast<const int64_t*>(val.data());
    ASSERT_EQ(val_int, 2);
    // Last state DROPPED
    reset_meta_service();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->atomic_add(tbl_version_key, 1);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    partition_pb.set_state(RecyclePartitionPB::DROPPED);
    val = partition_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(partition_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->commit_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(partition_pb.ParseFromString(val));
    ASSERT_EQ(partition_pb.state(), RecyclePartitionPB::DROPPED);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_OK);
    val_int = *reinterpret_cast<const int64_t*>(val.data());
    ASSERT_EQ(val_int, 1);
    // Last state RECYCLING
    reset_meta_service();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->atomic_add(tbl_version_key, 1);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    partition_pb.set_state(RecyclePartitionPB::RECYCLING);
    val = partition_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(partition_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->commit_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(partition_pb.ParseFromString(val));
    ASSERT_EQ(partition_pb.state(), RecyclePartitionPB::RECYCLING);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_OK);
    val_int = *reinterpret_cast<const int64_t*>(val.data());
    ASSERT_EQ(val_int, 1);
    // Last state UNKNOWN but tablet meta existed
    reset_meta_service();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->atomic_add(tbl_version_key, 1);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(tablet_key, tablet_val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->commit_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_OK);
    val_int = *reinterpret_cast<const int64_t*>(val.data());
    ASSERT_EQ(val_int, 1);
    // Last state UNKNOWN and tablet meta existed, but request has no index ids
    reset_meta_service();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(tablet_key, tablet_val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    req.clear_index_ids();
    meta_service->commit_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
    req.add_index_ids(index_id);
    // ------------Test check partition-----------
    // Normal
    req.set_db_id(1);
    req.set_table_id(table_id + 1);
    req.add_index_ids(index_id + 1);
    req.add_partition_ids(partition_id + 1);
    meta_service->prepare_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    meta_service->commit_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    CheckKVRequest req_check;
    CheckKVResponse res_check;
    meta_service->check_kv(&ctrl, &req_check, &res_check, nullptr);
    ASSERT_EQ(res_check.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    res_check.Clear();
    req_check.set_op(CheckKVRequest::CREATE_PARTITION_AFTER_FE_COMMIT);
    CheckKeyInfos check_keys_pb;
    check_keys_pb.add_table_ids(table_id + 1);
    check_keys_pb.add_index_ids(index_id + 1);
    check_keys_pb.add_partition_ids(partition_id + 1);
    req_check.mutable_check_keys()->CopyFrom(check_keys_pb);
    meta_service->check_kv(&ctrl, &req_check, &res_check, nullptr);
    ASSERT_EQ(res_check.status().code(), MetaServiceCode::OK);
    res_check.Clear();
    // AbNomal not commit
    req.Clear();
    req.set_db_id(1);
    req.set_table_id(table_id + 2);
    req.add_index_ids(index_id + 2);
    req.add_partition_ids(partition_id + 2);
    meta_service->prepare_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    req_check.Clear();
    req_check.set_op(CheckKVRequest::CREATE_PARTITION_AFTER_FE_COMMIT);
    check_keys_pb.Clear();
    check_keys_pb.add_table_ids(table_id + 2);
    check_keys_pb.add_index_ids(index_id + 2);
    check_keys_pb.add_partition_ids(partition_id + 2);
    req_check.mutable_check_keys()->CopyFrom(check_keys_pb);
    meta_service->check_kv(&ctrl, &req_check, &res_check, nullptr);
    ASSERT_EQ(res_check.status().code(), MetaServiceCode::UNDEFINED_ERR);

    // ------------Test check index-----------
    // Normal
    IndexRequest req_index;
    IndexResponse res_index;
    req_index.set_db_id(1);
    req_index.set_table_id(table_id + 3);
    req_index.add_index_ids(index_id + 3);
    meta_service->prepare_index(&ctrl, &req_index, &res_index, nullptr);
    ASSERT_EQ(res_index.status().code(), MetaServiceCode::OK);
    meta_service->commit_index(&ctrl, &req_index, &res_index, nullptr);
    ASSERT_EQ(res_index.status().code(), MetaServiceCode::OK);
    req_check.Clear();
    res_check.Clear();
    req_check.set_op(CheckKVRequest::CREATE_INDEX_AFTER_FE_COMMIT);
    check_keys_pb.Clear();
    check_keys_pb.add_table_ids(table_id + 3);
    check_keys_pb.add_index_ids(index_id + 3);
    req_check.mutable_check_keys()->CopyFrom(check_keys_pb);
    meta_service->check_kv(&ctrl, &req_check, &res_check, nullptr);
    ASSERT_EQ(res_check.status().code(), MetaServiceCode::OK);
    res_check.Clear();

    // ------------Test drop partition------------
    reset_meta_service();
    req.Clear();
    meta_service->drop_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    req.set_db_id(1);
    req.set_table_id(table_id);
    req.add_index_ids(index_id);
    req.add_partition_ids(partition_id);
    req.set_need_update_table_version(true);
    // Last state UNKNOWN
    res.Clear();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->atomic_add(tbl_version_key, 1);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    meta_service->drop_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(partition_pb.ParseFromString(val));
    ASSERT_EQ(partition_pb.state(), RecyclePartitionPB::DROPPED);
    // Drop partition should update table version
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_OK);
    val_int = *reinterpret_cast<const int64_t*>(val.data());
    ASSERT_EQ(val_int, 2);
    // Last state PREPARED
    reset_meta_service();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->atomic_add(tbl_version_key, 1);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    partition_pb.set_state(RecyclePartitionPB::PREPARED);
    val = partition_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(partition_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->drop_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(partition_pb.ParseFromString(val));
    ASSERT_EQ(partition_pb.state(), RecyclePartitionPB::DROPPED);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_OK);
    val_int = *reinterpret_cast<const int64_t*>(val.data());
    ASSERT_EQ(val_int, 2);
    // Last state PREPARED but drop an empty partition
    req.set_need_update_table_version(false);
    reset_meta_service();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->atomic_add(tbl_version_key, 1);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    partition_pb.set_state(RecyclePartitionPB::PREPARED);
    val = partition_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(partition_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->drop_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(partition_pb.ParseFromString(val));
    ASSERT_EQ(partition_pb.state(), RecyclePartitionPB::DROPPED);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_OK);
    val_int = *reinterpret_cast<const int64_t*>(val.data());
    ASSERT_EQ(val_int, 1);
    // Last state DROPPED
    reset_meta_service();
    req.set_need_update_table_version(true);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->atomic_add(tbl_version_key, 1);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    partition_pb.set_state(RecyclePartitionPB::DROPPED);
    val = partition_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(partition_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->drop_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(partition_pb.ParseFromString(val));
    ASSERT_EQ(partition_pb.state(), RecyclePartitionPB::DROPPED);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_OK);
    val_int = *reinterpret_cast<const int64_t*>(val.data());
    ASSERT_EQ(val_int, 1);
    // Last state RECYCLING
    reset_meta_service();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->atomic_add(tbl_version_key, 1);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    partition_pb.set_state(RecyclePartitionPB::RECYCLING);
    val = partition_pb.SerializeAsString();
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    txn->put(partition_key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    res.Clear();
    meta_service->drop_partition(&ctrl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(partition_key, &val), TxnErrorCode::TXN_OK);
    ASSERT_TRUE(partition_pb.ParseFromString(val));
    ASSERT_EQ(partition_pb.state(), RecyclePartitionPB::RECYCLING);
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    ASSERT_EQ(txn->get(tbl_version_key, &val), TxnErrorCode::TXN_OK);
    val_int = *reinterpret_cast<const int64_t*>(val.data());
    ASSERT_EQ(val_int, 1);
}

TEST(MetaServiceTxnStoreRetryableTest, MockGetVersion) {
    size_t index = 0;
    SyncPoint::get_instance()->set_call_back("get_version_code", [&](auto&& args) {
        LOG(INFO) << "GET_VERSION_CODE";
        if (++index < 2) {
            *doris::try_any_cast<MetaServiceCode*>(args[0]) =
                    MetaServiceCode::KV_TXN_STORE_GET_RETRYABLE;
        }
    });
    SyncPoint::get_instance()->enable_processing();

    auto service = get_meta_service();
    create_tablet(service.get(), 1, 1, 1, 1);
    insert_rowset(service.get(), 1, std::to_string(1), 1, 1, 1);

    brpc::Controller ctrl;
    GetVersionRequest req;
    req.set_cloud_unique_id("test_cloud_unique_id");
    req.set_db_id(1);
    req.set_table_id(1);
    req.set_partition_id(1);

    GetVersionResponse resp;
    service->get_version(&ctrl, &req, &resp, nullptr);

    ASSERT_EQ(resp.status().code(), MetaServiceCode::OK)
            << " status is " << resp.status().msg() << ", code=" << resp.status().code();
    EXPECT_EQ(resp.version(), 2);
    EXPECT_GE(index, 2);

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTxnStoreRetryableTest, DoNotReturnRetryableCode) {
    SyncPoint::get_instance()->set_call_back("get_version_code", [&](auto&& args) {
        *doris::try_any_cast<MetaServiceCode*>(args[0]) =
                MetaServiceCode::KV_TXN_STORE_GET_RETRYABLE;
    });
    SyncPoint::get_instance()->enable_processing();
    int32_t retry_times = config::txn_store_retry_times;
    config::txn_store_retry_times = 3;

    auto service = get_meta_service();
    create_tablet(service.get(), 1, 1, 1, 1);
    insert_rowset(service.get(), 1, std::to_string(1), 1, 1, 1);

    brpc::Controller ctrl;
    GetVersionRequest req;
    req.set_cloud_unique_id("test_cloud_unique_id");
    req.set_db_id(1);
    req.set_table_id(1);
    req.set_partition_id(1);

    GetVersionResponse resp;
    service->get_version(&ctrl, &req, &resp, nullptr);

    ASSERT_EQ(resp.status().code(), MetaServiceCode::KV_TXN_GET_ERR)
            << " status is " << resp.status().msg() << ", code=" << resp.status().code();

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
    config::txn_store_retry_times = retry_times;
}

TEST(MetaServiceTest, GetClusterStatusTest) {
    auto meta_service = get_meta_service();

    // add cluster first
    InstanceKeyInfo key_info {mock_instance};
    std::string key;
    std::string val;
    instance_key(key_info, &key);

    InstanceInfoPB instance;
    instance.set_instance_id(mock_instance);
    ClusterPB c1;
    c1.set_type(ClusterPB::COMPUTE);
    c1.set_cluster_name(mock_cluster_name);
    c1.set_cluster_id(mock_cluster_id);
    c1.add_mysql_user_name()->append("m1");
    c1.set_cluster_status(ClusterStatus::NORMAL);
    ClusterPB c2;
    c2.set_type(ClusterPB::COMPUTE);
    c2.set_cluster_name(mock_cluster_name + "2");
    c2.set_cluster_id(mock_cluster_id + "2");
    c2.add_mysql_user_name()->append("m2");
    c2.set_cluster_status(ClusterStatus::SUSPENDED);
    ClusterPB c3;
    c3.set_type(ClusterPB::COMPUTE);
    c3.set_cluster_name(mock_cluster_name + "3");
    c3.set_cluster_id(mock_cluster_id + "3");
    c3.add_mysql_user_name()->append("m3");
    c3.set_cluster_status(ClusterStatus::TO_RESUME);
    instance.add_clusters()->CopyFrom(c1);
    instance.add_clusters()->CopyFrom(c2);
    instance.add_clusters()->CopyFrom(c3);
    val = instance.SerializeAsString();

    std::unique_ptr<Transaction> txn;
    std::string get_val;
    TxnErrorCode err = meta_service->txn_kv()->create_txn(&txn);
    ASSERT_EQ(err, TxnErrorCode::TXN_OK);
    txn->put(key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

    // case: get all cluster
    {
        brpc::Controller cntl;
        GetClusterStatusRequest req;
        req.add_instance_ids(mock_instance);
        GetClusterStatusResponse res;
        meta_service->get_cluster_status(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.details().at(0).clusters().size(), 3);
    }

    // get normal cluster
    {
        brpc::Controller cntl;
        GetClusterStatusRequest req;
        req.add_instance_ids(mock_instance);
        req.set_status(NORMAL);
        GetClusterStatusResponse res;
        meta_service->get_cluster_status(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        ASSERT_EQ(res.details().at(0).clusters().size(), 1);
    }
}

TEST(MetaServiceTest, DecryptInfoTest) {
    auto meta_service = get_meta_service();
    auto sp = SyncPoint::get_instance();
    sp->enable_processing();
    sp->set_call_back("decrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* key = try_any_cast<std::string*>(args[0]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* ret = try_any_cast<int*>(args[1]);
        *ret = 0;
    });
    InstanceInfoPB instance;

    EncryptionInfoPB encryption_info;
    encryption_info.set_encryption_method("AES_256_ECB");
    encryption_info.set_key_id(1);

    std::string cipher_sk = "JUkuTDctR+ckJtnPkLScWaQZRcOtWBhsLLpnCRxQLxr734qB8cs6gNLH6grE1FxO";
    std::string plain_sk = "Hx60p12123af234541nsVsffdfsdfghsdfhsdf34t";
    ObjectStoreInfoPB obj_info;
    obj_info.mutable_encryption_info()->CopyFrom(encryption_info);
    obj_info.set_ak("akak1");
    obj_info.set_sk(cipher_sk);
    instance.add_obj_info()->CopyFrom(obj_info);

    RamUserPB ram_user;
    ram_user.set_ak("akak2");
    ram_user.set_sk(cipher_sk);
    ram_user.mutable_encryption_info()->CopyFrom(encryption_info);
    instance.mutable_ram_user()->CopyFrom(ram_user);

    StagePB stage;
    stage.mutable_obj_info()->CopyFrom(obj_info);
    instance.add_stages()->CopyFrom(stage);

    auto checkcheck = [&](const InstanceInfoPB& instance) {
        ASSERT_EQ(instance.obj_info(0).ak(), "akak1");
        ASSERT_EQ(instance.obj_info(0).sk(), plain_sk);

        ASSERT_EQ(instance.ram_user().ak(), "akak2");
        ASSERT_EQ(instance.ram_user().sk(), plain_sk);

        ASSERT_EQ(instance.stages(0).obj_info().ak(), "akak1");
        ASSERT_EQ(instance.stages(0).obj_info().sk(), plain_sk);
    };

    std::string instance_id = "i1";
    MetaServiceCode code;
    std::string msg;
    // No system_meta_service_arn_info_key
    {
        std::unique_ptr<Transaction> txn0;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn0), TxnErrorCode::TXN_OK);
        std::shared_ptr<Transaction> txn(txn0.release());
        InstanceInfoPB decrypt_instance;
        decrypt_instance.CopyFrom(instance);
        int ret = decrypt_instance_info(decrypt_instance, instance_id, code, msg, txn);
        ASSERT_EQ(ret, 0);
        checkcheck(decrypt_instance);
        ASSERT_EQ(decrypt_instance.iam_user().user_id(), config::arn_id);
        ASSERT_EQ(decrypt_instance.iam_user().external_id(), instance_id);
        ASSERT_EQ(decrypt_instance.iam_user().ak(), config::arn_ak);
        ASSERT_EQ(decrypt_instance.iam_user().sk(), config::arn_sk);
    }

    // With system_meta_service_arn_info_key
    {
        std::string key = system_meta_service_arn_info_key();
        std::string val;
        std::unique_ptr<Transaction> txn2;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn2), TxnErrorCode::TXN_OK);
        RamUserPB iam_user;
        iam_user.set_user_id("1234");
        iam_user.set_ak("aksk3");
        iam_user.set_sk(cipher_sk);
        iam_user.set_external_id(instance_id);
        iam_user.mutable_encryption_info()->CopyFrom(encryption_info);
        val = iam_user.SerializeAsString();
        txn2->put(key, val);
        ASSERT_EQ(txn2->commit(), TxnErrorCode::TXN_OK);
        std::unique_ptr<Transaction> txn0;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn0), TxnErrorCode::TXN_OK);
        std::shared_ptr<Transaction> txn(txn0.release());
        InstanceInfoPB decrypt_instance;
        decrypt_instance.CopyFrom(instance);
        int ret = decrypt_instance_info(decrypt_instance, instance_id, code, msg, txn);
        ASSERT_EQ(ret, 0);
        checkcheck(decrypt_instance);
        ASSERT_EQ(decrypt_instance.iam_user().user_id(), "1234");
        ASSERT_EQ(decrypt_instance.iam_user().external_id(), instance_id);
        ASSERT_EQ(decrypt_instance.iam_user().ak(), "aksk3");
        ASSERT_EQ(decrypt_instance.iam_user().sk(), plain_sk);
    }
    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, LegacyUpdateAkSkTest) {
    auto meta_service = get_meta_service();

    auto sp = SyncPoint::get_instance();
    sp->enable_processing();

    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });

    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    std::string key;
    std::string val;
    InstanceKeyInfo key_info {"test_instance"};
    instance_key(key_info, &key);

    ObjectStoreInfoPB obj_info;
    obj_info.set_id("1");
    obj_info.set_ak("ak");
    obj_info.set_sk("sk");
    InstanceInfoPB instance;
    instance.add_obj_info()->CopyFrom(obj_info);
    val = instance.SerializeAsString();
    txn->put(key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

    auto get_test_instance = [&](InstanceInfoPB& i) {
        std::string key;
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        InstanceKeyInfo key_info {"test_instance"};
        instance_key(key_info, &key);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    std::string cipher_sk = "JUkuTDctR+ckJtnPkLScWaQZRcOtWBhsLLpnCRxQLxr734qB8cs6gNLH6grE1FxO";
    std::string plain_sk = "Hx60p12123af234541nsVsffdfsdfghsdfhsdf34t";

    // update failed
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::LEGACY_UPDATE_AK_SK);
        req.mutable_obj()->set_id("2");
        req.mutable_obj()->set_ak("new_ak");
        req.mutable_obj()->set_sk(plain_sk);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_obj_store_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        InstanceInfoPB instance;
        get_test_instance(instance);
        ASSERT_EQ(instance.obj_info(0).id(), "1");
        ASSERT_EQ(instance.obj_info(0).ak(), "ak");
        ASSERT_EQ(instance.obj_info(0).sk(), "sk");
    }

    // update successful
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::LEGACY_UPDATE_AK_SK);
        req.mutable_obj()->set_id("1");
        req.mutable_obj()->set_ak("new_ak");
        req.mutable_obj()->set_sk(plain_sk);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_obj_store_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        InstanceInfoPB instance;
        get_test_instance(instance);
        ASSERT_EQ(instance.obj_info(0).id(), "1");
        ASSERT_EQ(instance.obj_info(0).ak(), "new_ak");
        ASSERT_EQ(instance.obj_info(0).sk(), cipher_sk);
    }

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

namespace detail {
bool normalize_hdfs_prefix(std::string& prefix);
bool normalize_hdfs_fs_name(std::string& fs_name);
} // namespace detail

TEST(MetaServiceTest, NormalizeHdfsConfTest) {
    using namespace detail;
    std::string prefix = "hdfs://127.0.0.1:8020/test";
    EXPECT_FALSE(normalize_hdfs_prefix(prefix));
    prefix = "test";
    EXPECT_TRUE(normalize_hdfs_prefix(prefix));
    EXPECT_EQ(prefix, "test");
    prefix = "   test ";
    EXPECT_TRUE(normalize_hdfs_prefix(prefix));
    EXPECT_EQ(prefix, "test");
    prefix = "  /test// ";
    EXPECT_TRUE(normalize_hdfs_prefix(prefix));
    EXPECT_EQ(prefix, "test");
    prefix = "/";
    EXPECT_TRUE(normalize_hdfs_prefix(prefix));
    EXPECT_EQ(prefix, "");

    std::string fs_name;
    EXPECT_FALSE(normalize_hdfs_fs_name(prefix));
    fs_name = " hdfs://127.0.0.1:8020/  ";
    EXPECT_TRUE(normalize_hdfs_fs_name(fs_name));
    EXPECT_EQ(fs_name, "hdfs://127.0.0.1:8020");
}

TEST(MetaServiceTest, AddObjInfoTest) {
    auto meta_service = get_meta_service();

    auto sp = SyncPoint::get_instance();
    sp->enable_processing();

    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });

    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    std::string key;
    std::string val;
    InstanceKeyInfo key_info {"test_instance"};
    instance_key(key_info, &key);

    InstanceInfoPB instance;
    val = instance.SerializeAsString();
    txn->put(key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

    auto get_test_instance = [&](InstanceInfoPB& i) {
        std::string key;
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        InstanceKeyInfo key_info {"test_instance"};
        instance_key(key_info, &key);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    // update failed
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_OBJ_INFO);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_obj_store_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT) << res.status().msg();
    }

    // update successful
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_OBJ_INFO);
        auto sp = SyncPoint::get_instance();
        sp->set_call_back("create_object_info_with_encrypt", [](auto&& args) {
            auto* ret = try_any_cast<int*>(args[0]);
            *ret = 0;
        });
        sp->enable_processing();

        ObjectStoreInfoPB obj_info;
        obj_info.set_ak("ak");
        obj_info.set_sk("sk");
        obj_info.set_bucket("bucket");
        obj_info.set_prefix("prefix");
        obj_info.set_endpoint("endpoint");
        obj_info.set_region("region");
        obj_info.set_provider(ObjectStoreInfoPB::Provider::ObjectStoreInfoPB_Provider_COS);
        obj_info.set_external_endpoint("external");
        req.mutable_obj()->MergeFrom(obj_info);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_obj_store_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();

        InstanceInfoPB instance;
        get_test_instance(instance);
        const auto& obj = instance.obj_info().at(0);
        ASSERT_EQ(obj.id(), "1");

        sp->clear_all_call_backs();
        sp->clear_trace();
        sp->disable_processing();
    }

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, AddHdfsInfoTest) {
    auto meta_service = get_meta_service();

    auto sp = SyncPoint::get_instance();
    sp->enable_processing();
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });

    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    std::string key;
    std::string val;
    InstanceKeyInfo key_info {"test_instance"};
    instance_key(key_info, &key);

    ObjectStoreInfoPB obj_info;
    obj_info.set_id("1");
    obj_info.set_ak("ak");
    obj_info.set_sk("sk");
    InstanceInfoPB instance;
    instance.add_obj_info()->CopyFrom(obj_info);
    val = instance.SerializeAsString();
    txn->put(key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

    auto get_test_instance = [&](InstanceInfoPB& i) {
        std::string key;
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        InstanceKeyInfo key_info {"test_instance"};
        instance_key(key_info, &key);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    // update failed
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_HDFS_INFO);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_obj_store_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT) << res.status().msg();
    }

    // update successful
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_HDFS_INFO);
        StorageVaultPB hdfs;
        hdfs.set_name("test_alter_add_hdfs_info");
        HdfsVaultInfo params;

        hdfs.mutable_hdfs_info()->CopyFrom(params);
        req.mutable_vault()->CopyFrom(hdfs);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        // Invalid fs name
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT) << res.status().msg();
        req.mutable_vault()->mutable_hdfs_info()->mutable_build_conf()->set_fs_name(
                "hdfs://ip:port");
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();

        InstanceInfoPB instance;
        get_test_instance(instance);
        ASSERT_EQ(*(instance.resource_ids().begin()), "2");
        ASSERT_EQ(*(instance.storage_vault_names().begin()), "test_alter_add_hdfs_info");
    }

    // update failed because duplicate name
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_HDFS_INFO);
        StorageVaultPB hdfs;
        hdfs.set_name("test_alter_add_hdfs_info");
        HdfsVaultInfo params;
        params.mutable_build_conf()->set_fs_name("hdfs://ip:port");

        hdfs.mutable_hdfs_info()->CopyFrom(params);
        req.mutable_vault()->CopyFrom(hdfs);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::ALREADY_EXISTED) << res.status().msg();
    }

    // to test if the vault id is expected
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_HDFS_INFO);
        StorageVaultPB hdfs;
        hdfs.set_name("test_alter_add_hdfs_info_1");
        HdfsVaultInfo params;
        params.mutable_build_conf()->set_fs_name("hdfs://ip:port");

        hdfs.mutable_hdfs_info()->CopyFrom(params);
        req.mutable_vault()->CopyFrom(hdfs);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        InstanceInfoPB instance;
        get_test_instance(instance);
        ASSERT_EQ(*(instance.resource_ids().begin() + 1), "3");
        ASSERT_EQ(*(instance.storage_vault_names().begin() + 1), "test_alter_add_hdfs_info_1");
    }

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, DropHdfsInfoTest) {
    auto meta_service = get_meta_service();

    auto sp = SyncPoint::get_instance();
    sp->enable_processing();
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });

    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    std::string key;
    std::string val;
    InstanceKeyInfo key_info {"test_instance"};
    instance_key(key_info, &key);

    ObjectStoreInfoPB obj_info;
    obj_info.set_id("1");
    obj_info.set_ak("ak");
    obj_info.set_sk("sk");
    StorageVaultPB vault;
    vault.set_name("test_hdfs_vault");
    vault.set_id("2");
    InstanceInfoPB instance;
    instance.add_obj_info()->CopyFrom(obj_info);
    instance.add_storage_vault_names(vault.name());
    instance.add_resource_ids(vault.id());
    val = instance.SerializeAsString();
    txn->put(key, val);
    txn->put(storage_vault_key({instance.instance_id(), "2"}), vault.SerializeAsString());
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    txn = nullptr;

    auto get_test_instance = [&](InstanceInfoPB& i) {
        std::string key;
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        InstanceKeyInfo key_info {"test_instance"};
        instance_key(key_info, &key);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    // update failed because has no storage vault set
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::DROP_HDFS_INFO);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT) << res.status().msg();
    }

    // update failed because vault name does not exist
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::DROP_HDFS_INFO);
        StorageVaultPB hdfs;
        hdfs.set_name("test_hdfs_vault_not_found");
        HdfsVaultInfo params;

        hdfs.mutable_hdfs_info()->CopyFrom(params);
        req.mutable_vault()->CopyFrom(hdfs);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::STORAGE_VAULT_NOT_FOUND)
                << res.status().msg();
    }

    // update successfully
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::DROP_HDFS_INFO);
        StorageVaultPB hdfs;
        hdfs.set_name("test_hdfs_vault");
        HdfsVaultInfo params;

        hdfs.mutable_hdfs_info()->CopyFrom(params);
        req.mutable_vault()->CopyFrom(hdfs);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        InstanceInfoPB instance;
        get_test_instance(instance);
        ASSERT_EQ(instance.resource_ids().size(), 0);
        ASSERT_EQ(instance.storage_vault_names().size(), 0);
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        // To test we can not read the storage vault anymore
        std::string vault_key = storage_vault_key({instance.instance_id(), "2"});
        std::string vault_value;
        auto code = txn->get(vault_key, &vault_value);
        ASSERT_TRUE(code != TxnErrorCode::TXN_OK);
    }

    {
        // Try to add one new hdfs info and then check the vault id is expected
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_HDFS_INFO);
        StorageVaultPB hdfs;
        hdfs.set_name("test_alter_add_hdfs_info");
        HdfsVaultInfo params;
        HdfsBuildConf conf;
        conf.set_fs_name("hdfs://127.0.0.1:8020");
        params.mutable_build_conf()->MergeFrom(conf);

        hdfs.mutable_hdfs_info()->CopyFrom(params);
        req.mutable_vault()->CopyFrom(hdfs);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        InstanceInfoPB instance;
        get_test_instance(instance);
        ASSERT_EQ(*(instance.resource_ids().begin()), "2");
        ASSERT_EQ(*(instance.storage_vault_names().begin()), "test_alter_add_hdfs_info");
    }

    // Add two more vaults
    {
        // Try to add one new hdfs info and then check the vault id is expected
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_HDFS_INFO);
        StorageVaultPB hdfs;
        hdfs.set_name("test_alter_add_hdfs_info_1");
        HdfsVaultInfo params;
        HdfsBuildConf conf;
        conf.set_fs_name("hdfs://127.0.0.1:8020");
        params.mutable_build_conf()->MergeFrom(conf);

        hdfs.mutable_hdfs_info()->CopyFrom(params);
        req.mutable_vault()->CopyFrom(hdfs);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        InstanceInfoPB instance;
        get_test_instance(instance);
        ASSERT_EQ(instance.resource_ids().at(1), "3");
        ASSERT_EQ(instance.storage_vault_names().at(1), "test_alter_add_hdfs_info_1");
    }

    {
        // Try to add one new hdfs info and then check the vault id is expected
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_HDFS_INFO);
        StorageVaultPB hdfs;
        hdfs.set_name("test_alter_add_hdfs_info_2");
        HdfsVaultInfo params;
        HdfsBuildConf conf;
        conf.set_fs_name("hdfs://127.0.0.1:8020");
        params.mutable_build_conf()->MergeFrom(conf);

        hdfs.mutable_hdfs_info()->CopyFrom(params);
        req.mutable_vault()->CopyFrom(hdfs);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        InstanceInfoPB instance;
        get_test_instance(instance);
        ASSERT_EQ(instance.resource_ids().at(2), "4");
        ASSERT_EQ(instance.storage_vault_names().at(2), "test_alter_add_hdfs_info_2");
    }

    // Remove one vault among three vaults
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::DROP_HDFS_INFO);
        StorageVaultPB hdfs;
        hdfs.set_name("test_alter_add_hdfs_info_1");
        HdfsVaultInfo params;

        hdfs.mutable_hdfs_info()->CopyFrom(params);
        req.mutable_vault()->CopyFrom(hdfs);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        InstanceInfoPB instance;
        get_test_instance(instance);
        ASSERT_EQ(instance.resource_ids().size(), 2);
        ASSERT_EQ(instance.storage_vault_names().size(), 2);
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        // To test we can not read the storage vault anymore
        std::string vault_key = storage_vault_key({instance.instance_id(), "3"});
        std::string vault_value;
        auto code = txn->get(vault_key, &vault_value);
        ASSERT_TRUE(code != TxnErrorCode::TXN_OK);
        ASSERT_EQ(2, instance.resource_ids().size());
        ASSERT_EQ(2, instance.storage_vault_names().size());
        ASSERT_EQ(instance.resource_ids().at(0), "2");
        ASSERT_EQ(instance.storage_vault_names().at(0), "test_alter_add_hdfs_info");
        ASSERT_EQ(instance.resource_ids().at(1), "4");
        ASSERT_EQ(instance.storage_vault_names().at(1), "test_alter_add_hdfs_info_2");
    }

    {
        // Try to add one new hdfs info and then check the vault id is expected
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_HDFS_INFO);
        StorageVaultPB hdfs;
        hdfs.set_name("test_alter_add_hdfs_info_3");
        HdfsVaultInfo params;
        HdfsBuildConf conf;
        conf.set_fs_name("hdfs://127.0.0.1:8020");
        params.mutable_build_conf()->MergeFrom(conf);

        hdfs.mutable_hdfs_info()->CopyFrom(params);
        req.mutable_vault()->CopyFrom(hdfs);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        InstanceInfoPB instance;
        get_test_instance(instance);
        ASSERT_EQ(instance.resource_ids().at(2), "5");
        ASSERT_EQ(instance.storage_vault_names().at(2), "test_alter_add_hdfs_info_3");
    }

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, GetDefaultVaultTest) {
    auto meta_service = get_meta_service();

    auto get_test_instance = [&](InstanceInfoPB& i, std::string instance_id) {
        std::string key;
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        InstanceKeyInfo key_info {std::move(instance_id)};
        instance_key(key_info, &key);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    // case: normal create instance with hdfs info
    {
        brpc::Controller cntl;
        CreateInstanceRequest req;
        std::string instance_id = "test_instance_with_hdfs_info";
        req.set_instance_id(instance_id);
        req.set_user_id("test_user");
        req.set_name("test_name");
        HdfsVaultInfo hdfs;
        HdfsBuildConf conf;
        conf.set_fs_name("hdfs://127.0.0.1:8020");
        conf.set_user("test_user");
        hdfs.mutable_build_conf()->CopyFrom(conf);
        StorageVaultPB vault;
        vault.mutable_hdfs_info()->CopyFrom(hdfs);
        req.mutable_vault()->CopyFrom(vault);

        auto sp = SyncPoint::get_instance();
        sp->set_call_back("create_object_info_with_encrypt", [](auto&& args) {
            auto* ret = try_any_cast<int*>(args[0]);
            *ret = 0;
        });
        sp->enable_processing();
        CreateInstanceResponse res;
        meta_service->create_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                      &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        InstanceInfoPB i;
        get_test_instance(i, instance_id);
        // It wouldn't be set
        ASSERT_EQ(i.default_storage_vault_id(), "");
        ASSERT_EQ(i.default_storage_vault_name(), "");
        ASSERT_EQ(i.resource_ids().at(0), "1");
        ASSERT_EQ(i.storage_vault_names().at(0), "built_in_storage_vault");
        sp->clear_all_call_backs();
        sp->clear_trace();
        sp->disable_processing();
    }

    // case: normal create instance with obj info
    {
        brpc::Controller cntl;
        CreateInstanceRequest req;
        std::string instance_id = "test_instance_with_s3_info";
        req.set_instance_id(instance_id);
        req.set_user_id("test_user");
        req.set_name("test_name");
        InstanceInfoPB instance;

        EncryptionInfoPB encryption_info;
        encryption_info.set_encryption_method("AES_256_ECB");
        encryption_info.set_key_id(1);

        std::string cipher_sk = "JUkuTDctR+ckJtnPkLScWaQZRcOtWBhsLLpnCRxQLxr734qB8cs6gNLH6grE1FxO";
        std::string plain_sk = "Hx60p12123af234541nsVsffdfsdfghsdfhsdf34t";
        ObjectStoreInfoPB obj_info;
        obj_info.mutable_encryption_info()->CopyFrom(encryption_info);
        obj_info.set_ak("akak1");
        obj_info.set_sk(cipher_sk);
        obj_info.set_endpoint("selectdb");
        obj_info.set_external_endpoint("velodb");
        obj_info.set_bucket("gavin");
        obj_info.set_region("American");
        obj_info.set_provider(ObjectStoreInfoPB::Provider::ObjectStoreInfoPB_Provider_S3);
        instance.add_obj_info()->CopyFrom(obj_info);
        req.mutable_obj_info()->CopyFrom(obj_info);

        auto sp = SyncPoint::get_instance();
        sp->set_call_back("create_object_info_with_encrypt", [](auto&& args) {
            auto* ret = try_any_cast<int*>(args[0]);
            *ret = 0;
            auto* code = try_any_cast<MetaServiceCode*>(args[1]);
            *code = MetaServiceCode::OK;
            auto* msg = try_any_cast<std::string*>(args[2]);
            *msg = "";
        });
        sp->enable_processing();
        CreateInstanceResponse res;
        meta_service->create_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                      &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        InstanceInfoPB i;
        get_test_instance(i, instance_id);
        sp->clear_all_call_backs();
        sp->clear_trace();
        sp->disable_processing();
    }
}

TEST(MetaServiceTest, SetDefaultVaultTest) {
    auto meta_service = get_meta_service();
    std::string instance_id = "test_instance";

    auto get_test_instance = [&](InstanceInfoPB& i) {
        std::string key;
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        InstanceKeyInfo key_info {instance_id};
        instance_key(key_info, &key);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    brpc::Controller cntl;
    CreateInstanceRequest req;
    req.set_instance_id(instance_id);
    req.set_user_id("test_user");
    req.set_name("test_name");
    HdfsVaultInfo hdfs;
    HdfsBuildConf conf;
    conf.set_fs_name("hdfs://127.0.0.1:8020");
    conf.set_user("test_user");
    hdfs.mutable_build_conf()->CopyFrom(conf);
    StorageVaultPB vault;
    vault.mutable_hdfs_info()->CopyFrom(hdfs);
    req.mutable_vault()->CopyFrom(vault);

    auto sp = SyncPoint::get_instance();
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "test";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });
    sp->enable_processing();
    CreateInstanceResponse res;
    meta_service->create_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                  &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    InstanceInfoPB i;
    get_test_instance(i);
    ASSERT_EQ(i.default_storage_vault_id(), "");
    ASSERT_EQ(i.default_storage_vault_name(), "");
    ASSERT_EQ(i.resource_ids().at(0), "1");
    ASSERT_EQ(i.storage_vault_names().at(0), "built_in_storage_vault");

    for (size_t i = 0; i < 20; i++) {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_HDFS_INFO);
        StorageVaultPB hdfs;
        auto name = fmt::format("test_alter_add_hdfs_info_{}", i);
        hdfs.set_name(name);
        HdfsVaultInfo params;
        HdfsBuildConf conf;
        conf.set_fs_name("hdfs://127.0.0.1:8020");
        params.mutable_build_conf()->MergeFrom(conf);

        hdfs.mutable_hdfs_info()->CopyFrom(params);
        req.mutable_vault()->CopyFrom(hdfs);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();

        AlterObjStoreInfoRequest set_default_req;
        set_default_req.set_cloud_unique_id("test_cloud_unique_id");
        set_default_req.set_op(AlterObjStoreInfoRequest::SET_DEFAULT_VAULT);
        set_default_req.mutable_vault()->CopyFrom(hdfs);
        AlterObjStoreInfoResponse set_default_res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &set_default_req,
                &set_default_res, nullptr);
        ASSERT_EQ(set_default_res.status().code(), MetaServiceCode::OK)
                << set_default_res.status().msg();

        InstanceInfoPB instance;
        get_test_instance(instance);
        ASSERT_EQ(std::to_string(i + 2), instance.default_storage_vault_id());
    }

    // Try to set one non-existent vault as default
    {
        StorageVaultPB hdfs;
        auto name = "test_alter_add_hdfs_info_no";
        hdfs.set_name(name);
        HdfsVaultInfo params;

        hdfs.mutable_hdfs_info()->CopyFrom(params);
        AlterObjStoreInfoRequest set_default_req;
        set_default_req.set_cloud_unique_id("test_cloud_unique_id");
        set_default_req.set_op(AlterObjStoreInfoRequest::SET_DEFAULT_VAULT);
        set_default_req.mutable_vault()->CopyFrom(hdfs);
        AlterObjStoreInfoResponse set_default_res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &set_default_req,
                &set_default_res, nullptr);
        ASSERT_NE(set_default_res.status().code(), MetaServiceCode::OK)
                << set_default_res.status().msg();
    }

    sp->clear_all_call_backs();
    sp->clear_trace();
    sp->disable_processing();
}

TEST(MetaServiceTest, GetObjStoreInfoTest) {
    auto meta_service = get_meta_service();

    auto sp = SyncPoint::get_instance();
    sp->enable_processing();
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });

    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    std::string key;
    std::string val;
    InstanceKeyInfo key_info {"test_instance"};
    instance_key(key_info, &key);

    ObjectStoreInfoPB obj_info;
    obj_info.set_id("1");
    obj_info.set_ak("ak");
    obj_info.set_sk("sk");
    StorageVaultPB vault;
    vault.set_name("test_hdfs_vault");
    vault.set_id("2");
    InstanceInfoPB instance;
    instance.add_obj_info()->CopyFrom(obj_info);
    instance.add_storage_vault_names(vault.name());
    instance.add_resource_ids(vault.id());
    instance.set_instance_id("GetObjStoreInfoTestInstance");
    val = instance.SerializeAsString();
    txn->put(key, val);
    txn->put(storage_vault_key({instance.instance_id(), "2"}), vault.SerializeAsString());
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    txn = nullptr;

    auto get_test_instance = [&](InstanceInfoPB& i) {
        std::string key;
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        InstanceKeyInfo key_info {"test_instance"};
        instance_key(key_info, &key);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    for (size_t i = 0; i < 20; i++) {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_HDFS_INFO);
        StorageVaultPB hdfs;
        auto name = fmt::format("test_alter_add_hdfs_info_{}", i);
        hdfs.set_name(name);
        HdfsVaultInfo params;
        HdfsBuildConf conf;
        conf.set_fs_name("hdfs://127.0.0.1:8020");
        params.mutable_build_conf()->MergeFrom(conf);

        hdfs.mutable_hdfs_info()->CopyFrom(params);
        req.mutable_vault()->CopyFrom(hdfs);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        InstanceInfoPB instance;
        get_test_instance(instance);
        ASSERT_TRUE(std::find_if(instance.resource_ids().begin(), instance.resource_ids().end(),
                                 [&](const auto& id) { return id == std::to_string(i + 3); }) !=
                    instance.resource_ids().end());
        ASSERT_TRUE(std::find_if(instance.storage_vault_names().begin(),
                                 instance.storage_vault_names().end(), [&](const auto& vault_name) {
                                     return name == vault_name;
                                 }) != instance.storage_vault_names().end());
    }

    {
        brpc::Controller cntl;
        GetObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        GetObjStoreInfoResponse res;
        meta_service->get_obj_store_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        ASSERT_EQ(res.storage_vault().size(), 21);
        const auto& vaults = res.storage_vault();
        for (size_t i = 0; i < 20; i++) {
            auto id = std::to_string(i + 3);
            auto name = fmt::format("test_alter_add_hdfs_info_{}", i);
            ASSERT_TRUE(std::find_if(vaults.begin(), vaults.end(), [&](const auto& vault) {
                            return id == vault.id();
                        }) != vaults.end());
            ASSERT_TRUE(std::find_if(vaults.begin(), vaults.end(), [&](const auto& vault) {
                            return name == vault.name();
                        }) != vaults.end());
        }
    }

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, CreateVersionedTablet) {
    auto meta_service = get_meta_service(false);

    auto sp = SyncPoint::get_instance();
    sp->enable_processing();
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });

    std::string instance_id = "test_instance";
    {
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        std::string key;
        std::string val;
        InstanceKeyInfo key_info {"test_instance"};
        instance_key(key_info, &key);

        InstanceInfoPB instance;
        instance.set_multi_version_status(MultiVersionStatus::MULTI_VERSION_WRITE_ONLY);
        val = instance.SerializeAsString();
        txn->put(key, val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

        meta_service->resource_mgr()->refresh_instance(instance_id);
    }

    int64_t db_id = 1, table_id = 2, index_id = 3, partition_id = 4, tablet_id = 5;

    {
        brpc::Controller cntl;
        CreateTabletsRequest req;
        CreateTabletsResponse res;
        req.set_cloud_unique_id(fmt::format("1:{}:1", instance_id));
        req.set_db_id(db_id);
        add_tablet(req, table_id, index_id, partition_id, tablet_id);
        meta_service->create_tablets(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << tablet_id;
    }

    Versionstamp commit_versionstamp;
    {
        // verify versioned tablet meta is written
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        std::string val;
        std::string key = versioned::meta_tablet_key({instance_id, tablet_id});
        ASSERT_EQ(versioned_get(txn.get(), key, &commit_versionstamp, &val), TxnErrorCode::TXN_OK)
                << hex(key);
        TabletMetaCloudPB versioned_tablet_meta;
        versioned_tablet_meta.ParseFromString(val);
        ASSERT_EQ(versioned_tablet_meta.table_id(), table_id);
        ASSERT_EQ(versioned_tablet_meta.index_id(), index_id)
                << versioned_tablet_meta.ShortDebugString();
        ASSERT_EQ(versioned_tablet_meta.partition_id(), partition_id);
        ASSERT_EQ(versioned_tablet_meta.tablet_id(), tablet_id);
    }

    {
        // verify versioned tablet index/inverted index is written
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        std::string key = versioned::tablet_index_key({instance_id, tablet_id});
        std::string val;
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        TabletIndexPB tablet_index;
        tablet_index.ParseFromString(val);
        ASSERT_EQ(tablet_index.db_id(), db_id);
        ASSERT_EQ(tablet_index.table_id(), table_id);
        ASSERT_EQ(tablet_index.index_id(), index_id);
        ASSERT_EQ(tablet_index.partition_id(), partition_id);
        ASSERT_EQ(tablet_index.tablet_id(), tablet_id);

        key = versioned::tablet_inverted_index_key(
                {instance_id, db_id, table_id, index_id, partition_id, tablet_id});
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
    }

    {
        // verify the first versioned rowset meta is written
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        int64_t end_version = 1; // from add_tablet
        std::string key = versioned::meta_rowset_load_key({instance_id, tablet_id, end_version});
        RowsetMetaCloudPB rowset_meta;
        Versionstamp versionstamp;
        ASSERT_EQ(versioned::document_get(txn.get(), key, &rowset_meta, &versionstamp),
                  TxnErrorCode::TXN_OK);
        ASSERT_EQ(versionstamp, commit_versionstamp);
    }

    {
        // verify the tablet load stats is written
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        std::string key = versioned::tablet_load_stats_key({instance_id, tablet_id});
        TabletStatsPB load_stats;
        Versionstamp versionstamp;
        ASSERT_EQ(versioned::document_get(txn.get(), key, &load_stats, &versionstamp),
                  TxnErrorCode::TXN_OK);
        ASSERT_EQ(versionstamp, commit_versionstamp);
    }

    {
        // verify the tablet compact stats is written
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        std::string key = versioned::tablet_compact_stats_key({instance_id, tablet_id});
        TabletStatsPB compact_stats;
        Versionstamp versionstamp;
        ASSERT_EQ(versioned::document_get(txn.get(), key, &compact_stats, &versionstamp),
                  TxnErrorCode::TXN_OK);
        ASSERT_EQ(versionstamp, commit_versionstamp);
        EXPECT_EQ(compact_stats.cumulative_point(), 2);
    }

    {
        // verify the tablet schema is written
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        std::string key = versioned::meta_schema_key({instance_id, index_id, 0});
        doris::TabletSchemaCloudPB schema;
        ASSERT_EQ(document_get(txn.get(), key, &schema), TxnErrorCode::TXN_OK);
        EXPECT_EQ(schema.schema_version(), 0);
    }

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, CreateTabletsVaultsTest) {
    auto meta_service = get_meta_service();

    auto sp = SyncPoint::get_instance();
    sp->enable_processing();
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });

    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    std::string key;
    std::string val;
    InstanceKeyInfo key_info {"test_instance"};
    instance_key(key_info, &key);

    InstanceInfoPB instance;
    instance.set_enable_storage_vault(true);
    val = instance.SerializeAsString();
    txn->put(key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

    auto get_test_instance = [&](InstanceInfoPB& i) {
        std::string key;
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        InstanceKeyInfo key_info {"test_instance"};
        instance_key(key_info, &key);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    // tablet_metas_size is 0
    {
        CreateTabletsRequest request;
        request.set_cloud_unique_id("test_cloud_unique_id");

        brpc::Controller cntl;
        CreateTabletsResponse response;
        meta_service->create_tablets(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &request, &response, nullptr);
        ASSERT_EQ(response.status().code(), MetaServiceCode::INVALID_ARGUMENT)
                << response.status().msg();
    }

    // try to use default
    {
        CreateTabletsRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_storage_vault_name("");
        req.add_tablet_metas();

        brpc::Controller cntl;
        CreateTabletsResponse res;
        meta_service->create_tablets(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        // failed because no default
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT) << res.status().msg();
    }

    // Create One Hdfs info as built_in vault
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_HDFS_INFO);
        StorageVaultPB hdfs;
        hdfs.set_name("built_in_storage_vault");
        HdfsVaultInfo params;
        params.mutable_build_conf()->set_fs_name("hdfs://ip:port");

        hdfs.mutable_hdfs_info()->CopyFrom(params);
        req.mutable_vault()->CopyFrom(hdfs);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();

        InstanceInfoPB i;
        get_test_instance(i);
        ASSERT_EQ(i.default_storage_vault_id(), "");
        ASSERT_EQ(i.default_storage_vault_name(), "");
        ASSERT_EQ(i.resource_ids().at(0), "1");
        ASSERT_EQ(i.storage_vault_names().at(0), "built_in_storage_vault");
    }

    // Try to set built_in_storage_vault vault as default
    {
        StorageVaultPB hdfs;
        std::string name = "built_in_storage_vault";
        hdfs.set_name(std::move(name));
        HdfsVaultInfo params;

        hdfs.mutable_hdfs_info()->CopyFrom(params);
        AlterObjStoreInfoRequest set_default_req;
        set_default_req.set_cloud_unique_id("test_cloud_unique_id");
        set_default_req.set_op(AlterObjStoreInfoRequest::SET_DEFAULT_VAULT);
        set_default_req.mutable_vault()->CopyFrom(hdfs);
        AlterObjStoreInfoResponse set_default_res;
        brpc::Controller cntl;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &set_default_req,
                &set_default_res, nullptr);
        ASSERT_EQ(set_default_res.status().code(), MetaServiceCode::OK)
                << set_default_res.status().msg();
    }

    // try to use default vault
    {
        CreateTabletsRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_storage_vault_name("");
        req.add_tablet_metas();

        auto sp = SyncPoint::get_instance();
        sp->set_call_back("create_tablets",
                          [](auto&& args) { *try_any_cast<bool*>(args.back()) = true; });
        sp->enable_processing();

        brpc::Controller cntl;
        CreateTabletsResponse res;
        meta_service->create_tablets(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        ASSERT_EQ(res.storage_vault_id(), "1");
        ASSERT_EQ(res.storage_vault_name(), "built_in_storage_vault");

        sp->clear_call_back("create_tablets");
    }

    // try to use one non-existent vault
    {
        CreateTabletsRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_storage_vault_name("non-existent");
        req.add_tablet_metas();

        auto sp = SyncPoint::get_instance();
        SyncPoint::CallbackGuard guard;
        sp->set_call_back(
                "create_tablets", [](auto&& args) { *try_any_cast<bool*>(args.back()) = true; },
                &guard);
        sp->enable_processing();

        brpc::Controller cntl;
        CreateTabletsResponse res;
        meta_service->create_tablets(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT) << res.status().msg();
    }

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, UpdateAkSkTest) {
    auto meta_service = get_meta_service();

    auto sp = SyncPoint::get_instance();
    sp->enable_processing();
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });

    auto get_test_instance = [&](InstanceInfoPB& i) {
        std::string key;
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        InstanceKeyInfo key_info {"test_instance"};
        instance_key(key_info, &key);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    std::string cipher_sk = "JUkuTDctR+ckJtnPkLScWaQZRcOtWBhsLLpnCRxQLxr734qB8cs6gNLH6grE1FxO";
    std::string plain_sk = "Hx60p12123af234541nsVsffdfsdfghsdfhsdf34t";

    auto update = [&](bool with_user_id, bool with_wrong_user_id) {
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        std::string key;
        std::string val;
        InstanceKeyInfo key_info {"test_instance"};
        instance_key(key_info, &key);

        ObjectStoreInfoPB obj_info;
        if (with_user_id) {
            obj_info.set_user_id("111");
        }
        obj_info.set_ak("ak");
        obj_info.set_sk("sk");
        InstanceInfoPB instance;
        instance.add_obj_info()->CopyFrom(obj_info);
        val = instance.SerializeAsString();
        txn->put(key, val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

        UpdateAkSkRequest req;
        req.set_instance_id("test_instance");
        RamUserPB ram_user;
        if (with_wrong_user_id) {
            ram_user.set_user_id("222");
        } else {
            ram_user.set_user_id("111");
        }
        ram_user.set_ak("new_ak");
        ram_user.set_sk(plain_sk);
        req.add_internal_bucket_user()->CopyFrom(ram_user);

        brpc::Controller cntl;
        UpdateAkSkResponse res;
        meta_service->update_ak_sk(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                   &req, &res, nullptr);
        if (with_wrong_user_id) {
            ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        } else {
            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
            InstanceInfoPB update_instance;
            get_test_instance(update_instance);
            ASSERT_EQ(update_instance.obj_info(0).user_id(), "111");
            ASSERT_EQ(update_instance.obj_info(0).ak(), "new_ak");
            ASSERT_EQ(update_instance.obj_info(0).sk(), cipher_sk);
        }
    };

    update(false, false);
    update(true, false);
    update(true, true);
}

TEST(MetaServiceTest, AlterIamTest) {
    auto meta_service = get_meta_service();

    auto sp = SyncPoint::get_instance();
    sp->enable_processing();
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });

    std::string cipher_sk = "JUkuTDctR+ckJtnPkLScWaQZRcOtWBhsLLpnCRxQLxr734qB8cs6gNLH6grE1FxO";
    std::string plain_sk = "Hx60p12123af234541nsVsffdfsdfghsdfhsdf34t";

    auto get_arn_info_key = [&](RamUserPB& i) {
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(system_meta_service_arn_info_key(), &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    // add new system_meta_service_arn_info_key
    {
        AlterIamRequest req;
        req.set_account_id("123");
        req.set_ak("ak1");
        req.set_sk(plain_sk);

        brpc::Controller cntl;
        AlterIamResponse res;
        meta_service->alter_iam(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        RamUserPB ram_user;
        get_arn_info_key(ram_user);
        ASSERT_EQ(ram_user.user_id(), "123");
        ASSERT_EQ(ram_user.ak(), "ak1");
        ASSERT_EQ(ram_user.sk(), cipher_sk);
    }
    // with old system_meta_service_arn_info_key
    {
        AlterIamRequest req;
        req.set_account_id("321");
        req.set_ak("ak2");
        req.set_sk(plain_sk);

        brpc::Controller cntl;
        AlterIamResponse res;
        meta_service->alter_iam(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        RamUserPB ram_user;
        get_arn_info_key(ram_user);
        ASSERT_EQ(ram_user.user_id(), "321");
        ASSERT_EQ(ram_user.ak(), "ak2");
        ASSERT_EQ(ram_user.sk(), cipher_sk);
    }

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, UpdateTmpRowsetTest) {
    auto meta_service = get_meta_service();

    std::string instance_id = "update_rowset_meta_test_instance_id";
    auto sp = SyncPoint::get_instance();
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
    };
    sp->set_call_back("get_instance_id", [&](auto&& args) {
        auto* ret = try_any_cast_ret<std::string>(args);
        ret->first = instance_id;
        ret->second = true;
    });
    sp->enable_processing();

    {
        // 1. normal path
        constexpr auto db_id = 10000, table_id = 10001, index_id = 10002, partition_id = 10003,
                       tablet_id = 10004;
        int64_t txn_id = 0;
        std::string label = "update_rowset_meta_test_label1";
        CreateRowsetResponse res;

        ASSERT_NO_FATAL_FAILURE(
                create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id));

        ASSERT_NO_FATAL_FAILURE(begin_txn(meta_service.get(), db_id, label, table_id, txn_id));
        auto rowset = create_rowset(txn_id, tablet_id, partition_id);
        ASSERT_NO_FATAL_FAILURE(prepare_rowset(meta_service.get(), rowset, res));
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;
        res.Clear();

        rowset.set_rowset_state(doris::BEGIN_PARTIAL_UPDATE);
        ASSERT_NO_FATAL_FAILURE(commit_rowset(meta_service.get(), rowset, res));
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;
        res.Clear();

        // simulate that there are new segments added to this rowset
        rowset.set_num_segments(rowset.num_segments() + 3);
        rowset.set_num_rows(rowset.num_rows() + 1000);
        rowset.set_total_disk_size(rowset.total_disk_size() + 11000);
        rowset.set_index_disk_size(rowset.index_disk_size() + 1000);
        rowset.set_data_disk_size(rowset.data_disk_size() + 10000);

        ASSERT_NO_FATAL_FAILURE(update_tmp_rowset(meta_service.get(), rowset, res));
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;

        std::string key;
        std::string val;
        MetaRowsetTmpKeyInfo key_info {instance_id, txn_id, tablet_id};
        meta_rowset_tmp_key(key_info, &key);
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        doris::RowsetMetaCloudPB fetchedRowsetMeta;
        ASSERT_TRUE(fetchedRowsetMeta.ParseFromString(val));

        ASSERT_EQ(doris::BEGIN_PARTIAL_UPDATE, fetchedRowsetMeta.rowset_state());
        ASSERT_EQ(rowset.num_segments(), fetchedRowsetMeta.num_segments());
        ASSERT_EQ(rowset.num_rows(), fetchedRowsetMeta.num_rows());
        ASSERT_EQ(rowset.total_disk_size(), fetchedRowsetMeta.total_disk_size());
        ASSERT_EQ(rowset.index_disk_size(), fetchedRowsetMeta.index_disk_size());
        ASSERT_EQ(rowset.data_disk_size(), fetchedRowsetMeta.data_disk_size());

        ASSERT_NO_FATAL_FAILURE(commit_txn(meta_service.get(), db_id, txn_id, label));
    }

    {
        // 2. rpc retryies due to network error will success
        constexpr auto db_id = 20000, table_id = 20001, index_id = 20002, partition_id = 20003,
                       tablet_id = 20004;
        int64_t txn_id = 0;
        std::string label = "update_rowset_meta_test_label2";
        CreateRowsetResponse res;

        ASSERT_NO_FATAL_FAILURE(
                create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id));

        ASSERT_NO_FATAL_FAILURE(begin_txn(meta_service.get(), db_id, label, table_id, txn_id));
        auto rowset = create_rowset(txn_id, tablet_id, partition_id);
        ASSERT_NO_FATAL_FAILURE(prepare_rowset(meta_service.get(), rowset, res));
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;
        res.Clear();

        rowset.set_rowset_state(doris::BEGIN_PARTIAL_UPDATE);
        ASSERT_NO_FATAL_FAILURE(commit_rowset(meta_service.get(), rowset, res));
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;
        res.Clear();

        // simulate that there are new segments added to this rowset
        rowset.set_num_segments(rowset.num_segments() + 3);
        rowset.set_num_rows(rowset.num_rows() + 1000);
        rowset.set_total_disk_size(rowset.total_disk_size() + 11000);
        rowset.set_index_disk_size(rowset.index_disk_size() + 1000);
        rowset.set_data_disk_size(rowset.data_disk_size() + 10000);

        // repeated calls to update_tmp_rowset will all success
        ASSERT_NO_FATAL_FAILURE(update_tmp_rowset(meta_service.get(), rowset, res));
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;
        ASSERT_NO_FATAL_FAILURE(update_tmp_rowset(meta_service.get(), rowset, res));
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;
        ASSERT_NO_FATAL_FAILURE(update_tmp_rowset(meta_service.get(), rowset, res));
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;
        ASSERT_NO_FATAL_FAILURE(update_tmp_rowset(meta_service.get(), rowset, res));
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;

        std::string key;
        std::string val;
        MetaRowsetTmpKeyInfo key_info {instance_id, txn_id, tablet_id};
        meta_rowset_tmp_key(key_info, &key);
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        doris::RowsetMetaCloudPB fetchedRowsetMeta;
        ASSERT_TRUE(fetchedRowsetMeta.ParseFromString(val));

        ASSERT_EQ(doris::BEGIN_PARTIAL_UPDATE, fetchedRowsetMeta.rowset_state());
        ASSERT_EQ(rowset.num_segments(), fetchedRowsetMeta.num_segments());
        ASSERT_EQ(rowset.num_rows(), fetchedRowsetMeta.num_rows());
        ASSERT_EQ(rowset.total_disk_size(), fetchedRowsetMeta.total_disk_size());
        ASSERT_EQ(rowset.index_disk_size(), fetchedRowsetMeta.index_disk_size());
        ASSERT_EQ(rowset.data_disk_size(), fetchedRowsetMeta.data_disk_size());

        ASSERT_NO_FATAL_FAILURE(commit_txn(meta_service.get(), db_id, txn_id, label));
    }

    {
        // 3. call update_tmp_rowset without commit_rowset first will fail
        constexpr auto db_id = 30000, table_id = 30001, index_id = 30002, partition_id = 30003,
                       tablet_id = 30004;
        int64_t txn_id = 0;
        std::string label = "update_rowset_meta_test_label1";
        CreateRowsetResponse res;

        ASSERT_NO_FATAL_FAILURE(
                create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id));

        ASSERT_NO_FATAL_FAILURE(begin_txn(meta_service.get(), db_id, label, table_id, txn_id));
        auto rowset = create_rowset(txn_id, tablet_id, partition_id);
        ASSERT_NO_FATAL_FAILURE(prepare_rowset(meta_service.get(), rowset, res));
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;
        res.Clear();

        // simulate that there are new segments added to this rowset
        rowset.set_num_segments(rowset.num_segments() + 3);
        rowset.set_num_rows(rowset.num_rows() + 1000);
        rowset.set_total_disk_size(rowset.total_disk_size() + 11000);
        rowset.set_index_disk_size(rowset.index_disk_size() + 1000);
        rowset.set_data_disk_size(rowset.data_disk_size() + 10000);

        ASSERT_NO_FATAL_FAILURE(update_tmp_rowset(meta_service.get(), rowset, res));
        ASSERT_EQ(res.status().code(), MetaServiceCode::ROWSET_META_NOT_FOUND) << label;
    }
}

TEST(MetaServiceTest, CreateS3VaultWithIamRole) {
    auto sp = SyncPoint::get_instance();
    sp->enable_processing();
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });
    std::pair<std::string, std::string> pair;
    sp->set_call_back("extract_object_storage_info:get_aksk_pair", [&](auto&& args) {
        auto* ret = try_any_cast<std::pair<std::string, std::string>*>(args[0]);
        pair = *ret;
    });

    auto meta_service = get_meta_service();

    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    std::string key;
    std::string val;
    InstanceKeyInfo key_info {"test_instance"};
    instance_key(key_info, &key);

    ObjectStoreInfoPB obj_info;
    obj_info.set_id("1");
    obj_info.set_ak("ak");
    obj_info.set_sk("sk");
    StorageVaultPB vault;
    constexpr char vault_name[] = "test_alter_s3_vault";
    vault.mutable_obj_info()->MergeFrom(obj_info);
    vault.set_name(vault_name);
    vault.set_id("2");
    InstanceInfoPB instance;
    instance.add_storage_vault_names(vault.name());
    instance.add_resource_ids(vault.id());
    instance.set_instance_id("GetObjStoreInfoTestInstance");
    instance.set_enable_storage_vault(true);
    val = instance.SerializeAsString();
    txn->put(key, val);
    txn->put(storage_vault_key({instance.instance_id(), "2"}), vault.SerializeAsString());
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    txn = nullptr;

    auto get_test_instance = [&](InstanceInfoPB& i) {
        std::string key;
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        InstanceKeyInfo key_info {"test_instance"};
        instance_key(key_info, &key);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_S3_VAULT);
        StorageVaultPB vault;
        vault.mutable_obj_info()->set_endpoint("s3.us-east-1.amazonaws.com");
        vault.mutable_obj_info()->set_region("us-east-1");
        vault.mutable_obj_info()->set_bucket("test_bucket");
        vault.mutable_obj_info()->set_prefix("test_prefix");
        vault.mutable_obj_info()->set_ak("new_ak");
        vault.mutable_obj_info()->set_sk("new_sk");
        vault.mutable_obj_info()->set_provider(
                ObjectStoreInfoPB::Provider::ObjectStoreInfoPB_Provider_S3);

        vault.set_name("ak_sk_s3_vault");
        req.mutable_vault()->CopyFrom(vault);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

        {
            InstanceInfoPB instance;
            get_test_instance(instance);
            std::unique_ptr<Transaction> txn;
            ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
            std::string val;
            ASSERT_EQ(txn->get(storage_vault_key({instance.instance_id(), "2"}), &val),
                      TxnErrorCode::TXN_OK);
            StorageVaultPB get_obj;
            get_obj.ParseFromString(val);
            ASSERT_EQ(get_obj.obj_info().ak(), "ak") << get_obj.obj_info().ak();
        }

        {
            InstanceInfoPB instance;
            get_test_instance(instance);
            std::unique_ptr<Transaction> txn;
            ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
            std::string val;
            ASSERT_EQ(txn->get(storage_vault_key({instance.instance_id(), "3"}), &val),
                      TxnErrorCode::TXN_OK);
            StorageVaultPB get_obj;
            get_obj.ParseFromString(val);
            ASSERT_EQ(get_obj.obj_info().ak(), "new_ak") << get_obj.obj_info().ak();
            ASSERT_NE(get_obj.obj_info().sk(), "new_sk") << get_obj.obj_info().sk();
        }
    }

    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_S3_VAULT);
        StorageVaultPB vault;
        vault.mutable_obj_info()->set_endpoint("s3.us-east-1.amazonaws.com");
        vault.mutable_obj_info()->set_region("us-east-1");
        vault.mutable_obj_info()->set_bucket("test_bucket");
        vault.mutable_obj_info()->set_prefix("test_prefix");
        vault.mutable_obj_info()->set_role_arn("arn:aws:iam::123456789012:role/test-role");
        vault.mutable_obj_info()->set_external_id("external_id");
        vault.mutable_obj_info()->set_provider(
                ObjectStoreInfoPB::Provider::ObjectStoreInfoPB_Provider_S3);
        vault.mutable_obj_info()->set_cred_provider_type(CredProviderTypePB::INSTANCE_PROFILE);

        vault.set_name("ak_sk_s3_vault_with_role");
        req.mutable_vault()->CopyFrom(vault);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

        {
            InstanceInfoPB instance;
            get_test_instance(instance);
            std::unique_ptr<Transaction> txn;
            ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
            std::string val;
            ASSERT_EQ(txn->get(storage_vault_key({instance.instance_id(), "4"}), &val),
                      TxnErrorCode::TXN_OK);
            StorageVaultPB get_obj;
            get_obj.ParseFromString(val);
            ASSERT_EQ(get_obj.obj_info().ak().empty(), true) << get_obj.obj_info().ak();
            ASSERT_EQ(get_obj.obj_info().sk().empty(), true) << get_obj.obj_info().sk();

            ASSERT_EQ(get_obj.obj_info().role_arn(), "arn:aws:iam::123456789012:role/test-role")
                    << get_obj.obj_info().role_arn();
            ASSERT_EQ(get_obj.obj_info().external_id(), "external_id")
                    << get_obj.obj_info().external_id();

            ASSERT_EQ(get_obj.obj_info().endpoint(), "s3.us-east-1.amazonaws.com")
                    << get_obj.obj_info().endpoint();
            ASSERT_EQ(get_obj.obj_info().region(), "us-east-1") << get_obj.obj_info().region();
            ASSERT_EQ(get_obj.obj_info().bucket(), "test_bucket") << get_obj.obj_info().bucket();
            ASSERT_EQ(get_obj.obj_info().prefix(), "test_prefix") << get_obj.obj_info().prefix();
            ASSERT_EQ(get_obj.obj_info().provider(),
                      ObjectStoreInfoPB::Provider::ObjectStoreInfoPB_Provider_S3)
                    << get_obj.obj_info().provider();
            ASSERT_EQ(get_obj.name(), "ak_sk_s3_vault_with_role") << get_obj.name();
            ASSERT_EQ(get_obj.id(), "4") << get_obj.id();
            ASSERT_EQ(get_obj.obj_info().cred_provider_type(), CredProviderTypePB::INSTANCE_PROFILE)
                    << get_obj.obj_info().cred_provider_type();
        }
    }

    LOG(INFO) << "instance:" << instance.ShortDebugString();
    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, AddObjInfoWithRole) {
    auto meta_service = get_meta_service();

    auto sp = SyncPoint::get_instance();
    sp->enable_processing();

    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });

    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    std::string key;
    std::string val;
    InstanceKeyInfo key_info {"test_instance"};
    instance_key(key_info, &key);

    InstanceInfoPB instance;
    val = instance.SerializeAsString();
    txn->put(key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

    auto get_test_instance = [&](InstanceInfoPB& i) {
        std::string key;
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        InstanceKeyInfo key_info {"test_instance"};
        instance_key(key_info, &key);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ADD_OBJ_INFO);
        auto sp = SyncPoint::get_instance();
        sp->set_call_back("create_object_info_with_encrypt", [](auto&& args) {
            auto* ret = try_any_cast<int*>(args[0]);
            *ret = 0;
        });
        sp->enable_processing();

        ObjectStoreInfoPB obj_info;
        obj_info.set_endpoint("s3.us-east-1.amazonaws.com");
        obj_info.set_region("us-east-1");
        obj_info.set_bucket("test_bucket");
        obj_info.set_prefix("test_prefix");
        obj_info.set_role_arn("arn:aws:iam::123456789012:role/test-role");
        obj_info.set_external_id("external_id");
        obj_info.set_provider(ObjectStoreInfoPB::Provider::ObjectStoreInfoPB_Provider_S3);
        obj_info.set_cred_provider_type(CredProviderTypePB::INSTANCE_PROFILE);

        req.mutable_obj()->MergeFrom(obj_info);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_obj_store_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();

        InstanceInfoPB instance;
        get_test_instance(instance);
        const auto& obj = instance.obj_info().at(0);
        ASSERT_EQ(obj.id(), "1");
        ASSERT_EQ(obj.ak().empty(), true) << obj.ak();
        ASSERT_EQ(obj.sk().empty(), true) << obj.sk();

        ASSERT_EQ(obj.role_arn(), "arn:aws:iam::123456789012:role/test-role") << obj.role_arn();
        ASSERT_EQ(obj.external_id(), "external_id") << obj.external_id();

        ASSERT_EQ(obj.endpoint(), "s3.us-east-1.amazonaws.com") << obj.endpoint();
        ASSERT_EQ(obj.region(), "us-east-1") << obj.region();
        ASSERT_EQ(obj.bucket(), "test_bucket") << obj.bucket();
        ASSERT_EQ(obj.prefix(), "test_prefix") << obj.prefix();
        ASSERT_EQ(obj.provider(), ObjectStoreInfoPB::Provider::ObjectStoreInfoPB_Provider_S3)
                << obj.provider();
        ASSERT_EQ(obj.cred_provider_type(), CredProviderTypePB::INSTANCE_PROFILE)
                << obj.cred_provider_type();

        sp->clear_all_call_backs();
        sp->clear_trace();
        sp->disable_processing();
    }

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, CheckJobExisted) {
    auto meta_service = get_meta_service();

    std::string instance_id = "check_job_existed_instance_id";
    auto sp = SyncPoint::get_instance();
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
    };
    sp->set_call_back("get_instance_id", [&](auto&& args) {
        auto* ret = try_any_cast_ret<std::string>(args);
        ret->first = instance_id;
        ret->second = true;
    });
    sp->enable_processing();

    // OK
    {
        constexpr auto table_id = 952701, index_id = 952702, partition_id = 952703,
                       tablet_id = 952704;
        int64_t txn_id = 952705;
        std::string label = "update_rowset_meta_test_label1";
        CreateRowsetResponse res;

        ASSERT_NO_FATAL_FAILURE(
                create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id));

        auto rowset = create_rowset(txn_id, tablet_id, partition_id);

        {
            StartTabletJobResponse res;
            start_compaction_job(meta_service.get(), tablet_id, "compaction1", "ip:port", 0, 0,
                                 TabletCompactionJobPB::BASE, res);
        }

        brpc::Controller cntl;
        auto arena = res.GetArena();
        auto req = google::protobuf::Arena::CreateMessage<CreateRowsetRequest>(arena);
        req->set_tablet_job_id("compaction1");
        req->mutable_rowset_meta()->CopyFrom(rowset);
        meta_service->prepare_rowset(&cntl, req, &res, nullptr);
        if (!arena) delete req;

        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        res.Clear();
    }

    // job does not exist,
    {
        constexpr auto table_id = 952801, index_id = 952802, partition_id = 952803,
                       tablet_id = 952804;
        int64_t txn_id = 952805;
        std::string label = "update_rowset_meta_test_label1";
        CreateRowsetResponse res;

        ASSERT_NO_FATAL_FAILURE(
                create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id));

        auto rowset = create_rowset(txn_id, tablet_id, partition_id);

        brpc::Controller cntl;
        auto arena = res.GetArena();
        auto req = google::protobuf::Arena::CreateMessage<CreateRowsetRequest>(arena);
        req->set_tablet_job_id("compaction1");
        req->mutable_rowset_meta()->CopyFrom(rowset);
        meta_service->prepare_rowset(&cntl, req, &res, nullptr);
        if (!arena) delete req;

        ASSERT_EQ(res.status().code(), MetaServiceCode::STALE_PREPARE_ROWSET) << res.status().msg();
        res.Clear();
    }

    // compaction job exists, job id not match
    {
        constexpr auto table_id = 952901, index_id = 952902, partition_id = 952903,
                       tablet_id = 952904;
        int64_t txn_id = 952905;
        std::string label = "update_rowset_meta_test_label1";
        CreateRowsetResponse res;

        ASSERT_NO_FATAL_FAILURE(
                create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id));

        auto rowset = create_rowset(txn_id, tablet_id, partition_id);

        {
            StartTabletJobResponse res;
            start_compaction_job(meta_service.get(), tablet_id, "compaction1", "ip:port", 0, 0,
                                 TabletCompactionJobPB::BASE, res);
        }

        brpc::Controller cntl;
        auto arena = res.GetArena();
        auto req = google::protobuf::Arena::CreateMessage<CreateRowsetRequest>(arena);
        req->set_tablet_job_id("compaction2");
        req->mutable_rowset_meta()->CopyFrom(rowset);
        meta_service->prepare_rowset(&cntl, req, &res, nullptr);
        if (!arena) delete req;

        ASSERT_EQ(res.status().code(), MetaServiceCode::STALE_PREPARE_ROWSET) << res.status().msg();
        res.Clear();
    }

    // do not set job id
    {
        constexpr auto table_id = 953501, index_id = 953502, partition_id = 953503,
                       tablet_id = 953504;
        int64_t txn_id = 953505;
        std::string label = "update_rowset_meta_test_label1";
        CreateRowsetResponse res;

        ASSERT_NO_FATAL_FAILURE(
                create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id));

        auto rowset = create_rowset(txn_id, tablet_id, partition_id);

        {
            StartTabletJobResponse res;
            start_compaction_job(meta_service.get(), tablet_id, "compaction1", "ip:port", 0, 0,
                                 TabletCompactionJobPB::BASE, res);
        }

        brpc::Controller cntl;
        auto arena = res.GetArena();
        auto req = google::protobuf::Arena::CreateMessage<CreateRowsetRequest>(arena);
        req->mutable_rowset_meta()->CopyFrom(rowset);
        meta_service->prepare_rowset(&cntl, req, &res, nullptr);
        if (!arena) delete req;

        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        res.Clear();
    }

    // job id is empty string
    {
        constexpr auto table_id = 953601, index_id = 953602, partition_id = 953603,
                       tablet_id = 953604;
        int64_t txn_id = 953605;
        std::string label = "update_rowset_meta_test_label1";
        CreateRowsetResponse res;

        ASSERT_NO_FATAL_FAILURE(
                create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id));

        auto rowset = create_rowset(txn_id, tablet_id, partition_id);

        {
            StartTabletJobResponse res;
            start_compaction_job(meta_service.get(), tablet_id, "compaction1", "ip:port", 0, 0,
                                 TabletCompactionJobPB::BASE, res);
        }

        brpc::Controller cntl;
        auto arena = res.GetArena();
        auto req = google::protobuf::Arena::CreateMessage<CreateRowsetRequest>(arena);
        req->set_tablet_job_id("");
        req->mutable_rowset_meta()->CopyFrom(rowset);
        meta_service->prepare_rowset(&cntl, req, &res, nullptr);
        if (!arena) delete req;

        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        res.Clear();
    }

    // commit rowset OK
    {
        constexpr auto table_id = 953001, index_id = 953002, partition_id = 953003,
                       tablet_id = 953004;
        int64_t txn_id = 953005;
        std::string label = "update_rowset_meta_test_label1";
        CreateRowsetResponse res;

        ASSERT_NO_FATAL_FAILURE(
                create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id));

        auto rowset = create_rowset(txn_id, tablet_id, partition_id);

        {
            StartTabletJobResponse res;
            start_compaction_job(meta_service.get(), tablet_id, "compaction1", "ip:port", 0, 0,
                                 TabletCompactionJobPB::BASE, res);
        }

        brpc::Controller cntl;
        auto arena = res.GetArena();
        auto req = google::protobuf::Arena::CreateMessage<CreateRowsetRequest>(arena);
        req->set_tablet_job_id("compaction1");
        req->mutable_rowset_meta()->CopyFrom(rowset);
        meta_service->commit_rowset(&cntl, req, &res, nullptr);
        if (!arena) delete req;

        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        res.Clear();
    }

    // commit rowset, job does not exist,
    {
        constexpr auto table_id = 953101, index_id = 953102, partition_id = 953103,
                       tablet_id = 953104;
        int64_t txn_id = 952805;
        std::string label = "update_rowset_meta_test_label1";
        CreateRowsetResponse res;

        ASSERT_NO_FATAL_FAILURE(
                create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id));

        auto rowset = create_rowset(txn_id, tablet_id, partition_id);

        brpc::Controller cntl;
        auto arena = res.GetArena();
        auto req = google::protobuf::Arena::CreateMessage<CreateRowsetRequest>(arena);
        req->set_tablet_job_id("compaction1");
        req->mutable_rowset_meta()->CopyFrom(rowset);
        meta_service->commit_rowset(&cntl, req, &res, nullptr);
        if (!arena) delete req;

        ASSERT_EQ(res.status().code(), MetaServiceCode::STALE_PREPARE_ROWSET) << res.status().msg();
        res.Clear();
    }

    // commit rowset, compaction job exists, job id not match
    {
        constexpr auto table_id = 953201, index_id = 953202, partition_id = 953203,
                       tablet_id = 953204;
        int64_t txn_id = 952905;
        std::string label = "update_rowset_meta_test_label1";
        CreateRowsetResponse res;

        ASSERT_NO_FATAL_FAILURE(
                create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id));

        auto rowset = create_rowset(txn_id, tablet_id, partition_id);

        {
            StartTabletJobResponse res;
            start_compaction_job(meta_service.get(), tablet_id, "compaction1", "ip:port", 0, 0,
                                 TabletCompactionJobPB::BASE, res);
        }

        brpc::Controller cntl;
        auto arena = res.GetArena();
        auto req = google::protobuf::Arena::CreateMessage<CreateRowsetRequest>(arena);
        req->set_tablet_job_id("compaction2");
        req->mutable_rowset_meta()->CopyFrom(rowset);
        meta_service->commit_rowset(&cntl, req, &res, nullptr);
        if (!arena) delete req;

        ASSERT_EQ(res.status().code(), MetaServiceCode::STALE_PREPARE_ROWSET) << res.status().msg();
        res.Clear();
    }

    // do not set job id when commit rowset
    {
        constexpr auto table_id = 953301, index_id = 953302, partition_id = 953303,
                       tablet_id = 953304;
        int64_t txn_id = 953305;
        std::string label = "update_rowset_meta_test_label1";
        CreateRowsetResponse res;

        ASSERT_NO_FATAL_FAILURE(
                create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id));

        auto rowset = create_rowset(txn_id, tablet_id, partition_id);

        {
            StartTabletJobResponse res;
            start_compaction_job(meta_service.get(), tablet_id, "compaction1", "ip:port", 0, 0,
                                 TabletCompactionJobPB::BASE, res);
        }

        brpc::Controller cntl;
        auto arena = res.GetArena();
        auto req = google::protobuf::Arena::CreateMessage<CreateRowsetRequest>(arena);
        req->mutable_rowset_meta()->CopyFrom(rowset);
        meta_service->commit_rowset(&cntl, req, &res, nullptr);
        if (!arena) delete req;

        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        res.Clear();
    }

    // job id is empty string when commit rowset
    {
        constexpr auto table_id = 953401, index_id = 953402, partition_id = 953403,
                       tablet_id = 953404;
        int64_t txn_id = 953405;
        std::string label = "update_rowset_meta_test_label1";
        CreateRowsetResponse res;

        ASSERT_NO_FATAL_FAILURE(
                create_tablet(meta_service.get(), table_id, index_id, partition_id, tablet_id));

        auto rowset = create_rowset(txn_id, tablet_id, partition_id);

        {
            StartTabletJobResponse res;
            start_compaction_job(meta_service.get(), tablet_id, "compaction1", "ip:port", 0, 0,
                                 TabletCompactionJobPB::BASE, res);
        }

        brpc::Controller cntl;
        auto arena = res.GetArena();
        auto req = google::protobuf::Arena::CreateMessage<CreateRowsetRequest>(arena);
        req->set_tablet_job_id("");
        req->mutable_rowset_meta()->CopyFrom(rowset);
        meta_service->commit_rowset(&cntl, req, &res, nullptr);
        if (!arena) delete req;

        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        res.Clear();
    }
}

TEST(MetaServiceTest, StalePrepareRowset) {
    auto meta_service = get_meta_service();

    int64_t table_id = 1;
    int64_t partition_id = 1;
    int64_t tablet_id = 1;
    int64_t db_id = 100201;
    std::string label = "test_prepare_rowset";
    create_tablet(meta_service.get(), table_id, 1, partition_id, tablet_id);

    int64_t txn_id = 0;
    ASSERT_NO_FATAL_FAILURE(begin_txn(meta_service.get(), db_id, label, table_id, txn_id));
    CreateRowsetResponse res;
    auto rowset = create_rowset(txn_id, tablet_id, partition_id);
    rowset.mutable_load_id()->set_hi(123);
    rowset.mutable_load_id()->set_lo(456);
    prepare_rowset(meta_service.get(), rowset, res);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;
    res.Clear();
    ASSERT_NO_FATAL_FAILURE(commit_rowset(meta_service.get(), rowset, res));
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;

    prepare_rowset(meta_service.get(), rowset, res);
    ASSERT_TRUE(res.status().msg().find("rowset already exists") != std::string::npos)
            << res.status().msg();
    ASSERT_EQ(res.status().code(), MetaServiceCode::ALREADY_EXISTED) << res.status().code();

    commit_txn(meta_service.get(), db_id, txn_id, label);
    prepare_rowset(meta_service.get(), rowset, res);
    ASSERT_TRUE(res.status().msg().find("txn is not in") != std::string::npos)
            << res.status().msg();
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT) << res.status().code();
}

TEST(MetaServiceTest, StaleCommitRowset) {
    auto meta_service = get_meta_service();

    int64_t table_id = 1;
    int64_t partition_id = 1;
    int64_t tablet_id = 1;
    int64_t db_id = 100201;
    std::string label = "test_prepare_rowset";
    create_tablet(meta_service.get(), table_id, 1, partition_id, tablet_id);

    int64_t txn_id = 0;
    ASSERT_NO_FATAL_FAILURE(begin_txn(meta_service.get(), db_id, label, table_id, txn_id));
    CreateRowsetResponse res;
    auto rowset = create_rowset(txn_id, tablet_id, partition_id);
    rowset.mutable_load_id()->set_hi(123);
    rowset.mutable_load_id()->set_lo(456);
    prepare_rowset(meta_service.get(), rowset, res);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;
    res.Clear();
    ASSERT_NO_FATAL_FAILURE(commit_rowset(meta_service.get(), rowset, res));
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;

    ASSERT_NO_FATAL_FAILURE(commit_rowset(meta_service.get(), rowset, res));
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << label;

    commit_txn(meta_service.get(), db_id, txn_id, label);
    ASSERT_NO_FATAL_FAILURE(commit_rowset(meta_service.get(), rowset, res));
    ASSERT_TRUE(res.status().msg().find("txn is not in") != std::string::npos)
            << res.status().msg();
    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT) << res.status().code();
}

TEST(MetaServiceTest, AlterObjInfoTest) {
    auto meta_service = get_meta_service();

    auto sp = SyncPoint::get_instance();
    sp->enable_processing();

    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });

    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    std::string key;
    std::string val;
    InstanceKeyInfo key_info {"test_instance"};
    instance_key(key_info, &key);

    ObjectStoreInfoPB obj_info;
    obj_info.set_id("1");
    obj_info.set_ak("access_key_132131");
    obj_info.set_sk("secret_key_434124");
    obj_info.set_provider(ObjectStoreInfoPB::Provider::ObjectStoreInfoPB_Provider_S3);
    InstanceInfoPB instance;
    instance.add_obj_info()->CopyFrom(obj_info);
    val = instance.SerializeAsString();
    txn->put(key, val);
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

    auto get_test_instance = [&](InstanceInfoPB& i) {
        std::string key;
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        InstanceKeyInfo key_info {"test_instance"};
        instance_key(key_info, &key);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    std::string cipher_sk = "JUkuTDctR+ckJtnPkLScWaQZRcOtWBhsLLpnCRxQLxr734qB8cs6gNLH6grE1FxO";
    std::string plain_sk = "Hx60p12123af234541nsVsffdfsdfghsdfhsdf34t";

    // update failed
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ALTER_OBJ_INFO);
        req.mutable_obj()->set_id("2");
        req.mutable_obj()->set_ak("new_ak");
        req.mutable_obj()->set_sk(plain_sk);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_obj_store_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        InstanceInfoPB instance;
        get_test_instance(instance);
        ASSERT_EQ(instance.obj_info(0).id(), "1");
        ASSERT_EQ(instance.obj_info(0).ak(), "access_key_132131");
        ASSERT_EQ(instance.obj_info(0).sk(), "secret_key_434124");
    }

    // update ak/sk successful
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ALTER_OBJ_INFO);
        req.mutable_obj()->set_id("1");
        req.mutable_obj()->set_ak("new_access_key_132131");
        req.mutable_obj()->set_sk(plain_sk);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_obj_store_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        InstanceInfoPB instance;
        get_test_instance(instance);
        LOG(INFO) << "instance:" << instance.ShortDebugString();
        ASSERT_EQ(instance.obj_info(0).id(), "1");
        ASSERT_EQ(instance.obj_info(0).ak(), "new_access_key_132131");
        ASSERT_EQ(instance.obj_info(0).sk(), cipher_sk);
    }

    // update from ak/sk to role_arn
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ALTER_OBJ_INFO);
        req.mutable_obj()->set_id("1");
        req.mutable_obj()->set_role_arn("arn:aws:iam::1453123012:role/test-role");
        req.mutable_obj()->set_external_id("external_id_13123");
        req.mutable_obj()->set_cred_provider_type(CredProviderTypePB::INSTANCE_PROFILE);
        req.mutable_obj()->set_provider(ObjectStoreInfoPB::Provider::ObjectStoreInfoPB_Provider_S3);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_obj_store_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        InstanceInfoPB instance;
        get_test_instance(instance);
        LOG(INFO) << "instance:" << instance.ShortDebugString();
        ASSERT_EQ(instance.obj_info(0).id(), "1");
        ASSERT_EQ(instance.obj_info(0).role_arn(), "arn:aws:iam::1453123012:role/test-role");
        ASSERT_EQ(instance.obj_info(0).external_id(), "external_id_13123");
        ASSERT_EQ(instance.obj_info(0).provider(),
                  ObjectStoreInfoPB::Provider::ObjectStoreInfoPB_Provider_S3);
        ASSERT_EQ(instance.obj_info(0).cred_provider_type(), CredProviderTypePB::INSTANCE_PROFILE);
        ASSERT_TRUE(instance.obj_info(0).ak().empty());
        ASSERT_TRUE(instance.obj_info(0).sk().empty());
        ASSERT_FALSE(instance.obj_info(0).has_encryption_info());
    }

    // update from role_arn to ak/sk
    {
        AlterObjStoreInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ALTER_OBJ_INFO);
        req.mutable_obj()->set_id("1");
        req.mutable_obj()->set_ak("new_access_key_132131");
        req.mutable_obj()->set_sk(plain_sk);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_obj_store_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        InstanceInfoPB instance;
        get_test_instance(instance);
        LOG(INFO) << "instance:" << instance.ShortDebugString();
        ASSERT_EQ(instance.obj_info(0).id(), "1");
        ASSERT_EQ(instance.obj_info(0).ak(), "new_access_key_132131");
        ASSERT_EQ(instance.obj_info(0).sk(), cipher_sk);
        ASSERT_EQ(instance.obj_info(0).provider(),
                  ObjectStoreInfoPB::Provider::ObjectStoreInfoPB_Provider_S3);
        ASSERT_FALSE(instance.obj_info(0).has_cred_provider_type());
        ASSERT_FALSE(instance.obj_info(0).has_role_arn());
        ASSERT_FALSE(instance.obj_info(0).has_external_id());
    }

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

TEST(MetaServiceTest, AlterS3StorageVaultWithRoleArnTest) {
    auto meta_service = get_meta_service();

    auto sp = SyncPoint::get_instance();
    sp->enable_processing();
    sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
        auto* ret = try_any_cast<int*>(args[0]);
        *ret = 0;
        auto* key = try_any_cast<std::string*>(args[1]);
        *key = "selectdbselectdbselectdbselectdb";
        auto* key_id = try_any_cast<int64_t*>(args[2]);
        *key_id = 1;
    });
    std::pair<std::string, std::string> pair;
    sp->set_call_back("extract_object_storage_info:get_aksk_pair", [&](auto&& args) {
        auto* ret = try_any_cast<std::pair<std::string, std::string>*>(args[0]);
        pair = *ret;
    });

    std::unique_ptr<Transaction> txn;
    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
    std::string key;
    std::string val;
    InstanceKeyInfo key_info {"test_instance"};
    instance_key(key_info, &key);

    ObjectStoreInfoPB obj_info;
    obj_info.set_id("1");
    obj_info.set_ak("123456ab");
    obj_info.set_sk("@ak$");
    obj_info.set_provider(ObjectStoreInfoPB::Provider::ObjectStoreInfoPB_Provider_S3);
    StorageVaultPB vault;
    constexpr char vault_name[] = "test_alter_s3_vault_111";
    vault.mutable_obj_info()->MergeFrom(obj_info);
    vault.set_name(vault_name);
    vault.set_id("2");
    InstanceInfoPB instance;
    instance.add_storage_vault_names(vault.name());
    instance.add_resource_ids(vault.id());
    instance.set_instance_id("GetObjStoreInfoTestInstance");
    val = instance.SerializeAsString();
    txn->put(key, val);
    txn->put(storage_vault_key({instance.instance_id(), "2"}), vault.SerializeAsString());
    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    txn = nullptr;

    auto get_test_instance = [&](InstanceInfoPB& i) {
        std::string key;
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        InstanceKeyInfo key_info {"test_instance"};
        instance_key(key_info, &key);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        i.ParseFromString(val);
    };

    // update from ak/sk to role_arn
    {
        AlterObjStoreInfoRequest req;
        constexpr char new_vault_name[] = "new_test_alter_s3_vault_111";
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ALTER_S3_VAULT);
        StorageVaultPB vault;
        vault.set_alter_name(new_vault_name);
        ObjectStoreInfoPB obj;
        obj.set_role_arn("arn:aws:iam::12311321:role/test-alter-role");
        obj.set_external_id("external_id_123123");
        vault.mutable_obj_info()->MergeFrom(obj);
        vault.set_name(vault_name);
        req.mutable_vault()->CopyFrom(vault);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();

        InstanceInfoPB instance;
        get_test_instance(instance);
        LOG(INFO) << "instance:" << instance.ShortDebugString();

        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        std::string val;
        ASSERT_EQ(txn->get(storage_vault_key({instance.instance_id(), "2"}), &val),
                  TxnErrorCode::TXN_OK);
        StorageVaultPB get_obj;
        get_obj.ParseFromString(val);
        ASSERT_EQ(get_obj.id(), "2");
        ASSERT_EQ(get_obj.obj_info().role_arn(), "arn:aws:iam::12311321:role/test-alter-role");
        ASSERT_EQ(get_obj.obj_info().external_id(), "external_id_123123");
        ASSERT_EQ(get_obj.obj_info().provider(),
                  ObjectStoreInfoPB::Provider::ObjectStoreInfoPB_Provider_S3);
        ASSERT_EQ(get_obj.obj_info().cred_provider_type(), CredProviderTypePB::INSTANCE_PROFILE);
        ASSERT_TRUE(get_obj.obj_info().ak().empty());
        ASSERT_TRUE(get_obj.obj_info().sk().empty());
        ASSERT_FALSE(get_obj.obj_info().has_encryption_info());
        ASSERT_EQ(get_obj.name(), new_vault_name) << get_obj.obj_info().ShortDebugString();
    }

    std::string cipher_sk = "JUkuTDctR+ckJtnPkLScWaQZRcOtWBhsLLpnCRxQLxr734qB8cs6gNLH6grE1FxO";
    std::string plain_sk = "Hx60p12123af234541nsVsffdfsdfghsdfhsdf34t";

    // update from role_arn to ak_sk
    {
        AlterObjStoreInfoRequest req;
        constexpr char new_vault_name[] = "new_test_alter_s3_vault_111";
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_op(AlterObjStoreInfoRequest::ALTER_S3_VAULT);
        StorageVaultPB vault;
        ObjectStoreInfoPB obj;
        obj.set_ak("123456ab");
        obj.set_sk(plain_sk);
        vault.mutable_obj_info()->MergeFrom(obj);
        vault.set_name(new_vault_name);
        req.mutable_vault()->CopyFrom(vault);

        brpc::Controller cntl;
        AlterObjStoreInfoResponse res;
        meta_service->alter_storage_vault(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();

        InstanceInfoPB instance;
        get_test_instance(instance);
        LOG(INFO) << "instance:" << instance.ShortDebugString();

        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        std::string val;
        ASSERT_EQ(txn->get(storage_vault_key({instance.instance_id(), "2"}), &val),
                  TxnErrorCode::TXN_OK);
        StorageVaultPB get_obj;
        get_obj.ParseFromString(val);
        ASSERT_EQ(get_obj.id(), "2");
        ASSERT_EQ(get_obj.obj_info().provider(),
                  ObjectStoreInfoPB::Provider::ObjectStoreInfoPB_Provider_S3);
        ASSERT_EQ(get_obj.obj_info().ak(), "123456ab");
        ASSERT_EQ(get_obj.obj_info().sk(), cipher_sk);
        ASSERT_TRUE(get_obj.obj_info().role_arn().empty());
        ASSERT_TRUE(get_obj.obj_info().external_id().empty());
        ASSERT_TRUE(get_obj.obj_info().has_encryption_info());
        ASSERT_FALSE(get_obj.obj_info().has_cred_provider_type());
        ASSERT_EQ(get_obj.name(), new_vault_name) << get_obj.obj_info().ShortDebugString();
    }

    SyncPoint::get_instance()->disable_processing();
    SyncPoint::get_instance()->clear_all_call_backs();
}

void scan_restore_job_rowset(
        Transaction* txn, const std::string& instance_id, int64_t tablet_id, MetaServiceCode& code,
        std::string& msg,
        std::vector<std::pair<std::string, doris::RowsetMetaCloudPB>>* restore_job_rs_metas);

TEST(MetaServiceTest, RestoreJobTest) {
    auto meta_service = get_meta_service();
    ASSERT_NE(meta_service, nullptr);

    std::string instance_id = "test_prepare_restore_job_instance_id";
    auto sp = SyncPoint::get_instance();
    std::unique_ptr<int, std::function<void(int*)>> defer(
            (int*)0x01, [](int*) { SyncPoint::get_instance()->clear_all_call_backs(); });
    sp->set_call_back("get_instance_id", [&](auto&& args) {
        auto* ret = try_any_cast_ret<std::string>(args);
        ret->first = instance_id;
        ret->second = true;
    });
    sp->enable_processing();

    auto reset_meta_service = [&meta_service] { meta_service = get_meta_service(); };
    constexpr int64_t table_id = 10001;
    constexpr int64_t index_id = 10002;
    constexpr int64_t partition_id = 10003;
    constexpr int64_t tablet_id = 10004;
    constexpr int64_t version = 1;
    constexpr int64_t txn_id = 0;

    TabletIndexPB tablet_idx;
    tablet_idx.set_table_id(table_id);
    tablet_idx.set_index_id(index_id);
    tablet_idx.set_partition_id(partition_id);
    tablet_idx.set_tablet_id(tablet_id);
    std::string tablet_idx_val;
    tablet_idx.SerializeToString(&tablet_idx_val);

    std::unique_ptr<Transaction> txn;
    brpc::Controller cntl;
    RestoreJobRequest req;
    RestoreJobResponse res;

    // ------------Test prepare restore job------------
    // invalid args prepare restore job
    {
        reset_meta_service();
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        txn->put(meta_tablet_idx_key({instance_id, tablet_id}), tablet_idx_val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
        // empty action
        meta_service->prepare_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_TRUE(res.status().msg().find("invalid action") != std::string::npos);
        req.set_action(RestoreJobRequest::PREPARE);

        // empty tablet id
        meta_service->prepare_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_EQ(res.status().msg(), "empty tablet_id");

        // restore with empty tablet meta
        req.set_tablet_id(tablet_id);
        res.Clear();
        meta_service->prepare_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_EQ(res.status().msg(), "no tablet meta");

        // check key existence
        std::string restore_job_key = job_restore_tablet_key({instance_id, tablet_id});
        std::string val;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(restore_job_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);

        req.Clear();
        res.Clear();
    }
    // normal prepare restore job
    {
        reset_meta_service();
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        txn->put(meta_tablet_idx_key({instance_id, tablet_id}), tablet_idx_val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
        req.set_tablet_id(tablet_id);
        req.set_expiration(time(nullptr) + 3600);
        req.set_action(RestoreJobRequest::PREPARE);

        // set tablet meta
        auto* tablet_meta = req.mutable_tablet_meta();
        tablet_meta->set_table_id(table_id);
        tablet_meta->set_index_id(index_id);
        tablet_meta->set_partition_id(partition_id);
        tablet_meta->set_tablet_id(tablet_id);
        tablet_meta->set_schema_version(1);
        auto* rs_meta = tablet_meta->add_rs_metas();
        *rs_meta = create_rowset(txn_id, tablet_id, partition_id, version);

        meta_service->prepare_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        std::string restore_job_key = job_restore_tablet_key({instance_id, tablet_id});
        std::string val;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(restore_job_key, &val), TxnErrorCode::TXN_OK);

        RestoreJobCloudPB restore_job_pb;
        ASSERT_TRUE(restore_job_pb.ParseFromString(val));
        ASSERT_EQ(restore_job_pb.tablet_id(), tablet_id);
        ASSERT_EQ(restore_job_pb.state(), RestoreJobCloudPB::PREPARED);
        ASSERT_EQ(restore_job_pb.tablet_meta().schema_version(), 1);

        std::string restore_job_rs_key = job_restore_rowset_key({instance_id, tablet_id, version});
        ASSERT_EQ(txn->get(restore_job_rs_key, &val), TxnErrorCode::TXN_OK);
        RowsetMetaCloudPB rs_meta_pb;
        ASSERT_TRUE(rs_meta_pb.ParseFromString(val));
        ASSERT_EQ(rs_meta_pb.tablet_id(), tablet_id);
        ASSERT_EQ(rs_meta_pb.rowset_id_v2(), rs_meta->rowset_id_v2());
        req.Clear();
        res.Clear();
    }
    // duplicate prepare restore job
    {
        reset_meta_service();
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        txn->put(meta_tablet_idx_key({instance_id, tablet_id}), tablet_idx_val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
        req.set_tablet_id(tablet_id);
        req.set_action(RestoreJobRequest::PREPARE);

        // set tablet meta
        auto* tablet_meta = req.mutable_tablet_meta();
        tablet_meta->set_table_id(table_id);
        tablet_meta->set_index_id(index_id);
        tablet_meta->set_partition_id(partition_id);
        tablet_meta->set_tablet_id(tablet_id);
        tablet_meta->set_schema_version(1);
        auto* rs_meta = tablet_meta->add_rs_metas();
        *rs_meta = create_rowset(txn_id, tablet_id, partition_id, version);

        // first request
        meta_service->prepare_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        std::string restore_job_key = job_restore_tablet_key({instance_id, tablet_id});
        std::string val;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(restore_job_key, &val), TxnErrorCode::TXN_OK);

        RestoreJobCloudPB restore_job_pb;
        ASSERT_TRUE(restore_job_pb.ParseFromString(val));
        ASSERT_EQ(restore_job_pb.tablet_id(), tablet_id);
        ASSERT_EQ(restore_job_pb.state(), RestoreJobCloudPB::PREPARED);
        ASSERT_EQ(restore_job_pb.version(), 0);

        // second request
        meta_service->prepare_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(restore_job_key, &val), TxnErrorCode::TXN_OK);

        restore_job_pb.Clear();
        ASSERT_TRUE(restore_job_pb.ParseFromString(val));
        ASSERT_EQ(restore_job_pb.tablet_id(), tablet_id);
        ASSERT_EQ(restore_job_pb.state(), RestoreJobCloudPB::PREPARED);
        ASSERT_EQ(restore_job_pb.version(), 1);
        req.Clear();
        res.Clear();
    }
    // ------------Test commit restore job------------
    // invalid args commit restore job
    {
        reset_meta_service();
        // empty action
        meta_service->commit_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_TRUE(res.status().msg().find("invalid action") != std::string::npos);
        req.set_action(RestoreJobRequest::COMMIT);

        // empty tablet_id
        meta_service->commit_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_EQ(res.status().msg(), "empty tablet_id");

        // check key existence
        std::string restore_job_key = job_restore_tablet_key({instance_id, tablet_id});
        std::string val;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(restore_job_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);

        req.Clear();
        res.Clear();
    }
    // commit restore job not exits
    {
        reset_meta_service();
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        txn->put(meta_tablet_idx_key({instance_id, tablet_id}), tablet_idx_val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

        req.set_tablet_id(tablet_id);
        req.set_action(RestoreJobRequest::COMMIT);
        meta_service->commit_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_EQ(res.status().msg(), "restore job not exists or has been recycled");
        req.Clear();
        res.Clear();
    }
    // normal commit restore job
    for (int store_version = 0; store_version < 4; store_version++) {
        reset_meta_service();
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        txn->put(meta_tablet_idx_key({instance_id, tablet_id}), tablet_idx_val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

        // prepare restore job
        RestoreJobRequest make_req;
        RestoreJobResponse make_res;
        make_req.set_tablet_id(tablet_id);
        make_req.set_expiration(time(nullptr) + 3600);
        make_req.set_action(RestoreJobRequest::PREPARE);
        auto* tablet_meta = make_req.mutable_tablet_meta();
        tablet_meta->set_table_id(table_id);
        tablet_meta->set_index_id(index_id);
        tablet_meta->set_partition_id(partition_id);
        tablet_meta->set_tablet_id(tablet_id);
        tablet_meta->set_schema_version(1);
        auto* rs_meta = tablet_meta->add_rs_metas();
        *rs_meta = create_rowset(txn_id, tablet_id, partition_id, version);
        auto* delete_bitmap = tablet_meta->mutable_delete_bitmap();
        // 1
        delete_bitmap->add_rowset_ids(rs_meta->rowset_id_v2());
        delete_bitmap->add_versions(1);
        delete_bitmap->add_segment_ids(1);
        delete_bitmap->add_segment_delete_bitmaps("test_bitmap");
        // 2
        delete_bitmap->add_rowset_ids(rs_meta->rowset_id_v2());
        delete_bitmap->add_versions(1);
        delete_bitmap->add_segment_ids(2);
        delete_bitmap->add_segment_delete_bitmaps("test_bitmap2");
        // 3
        delete_bitmap->add_rowset_ids(rs_meta->rowset_id_v2() + "2");
        delete_bitmap->add_versions(1);
        delete_bitmap->add_segment_ids(1);
        delete_bitmap->add_segment_delete_bitmaps("test_bitmap3");

        meta_service->prepare_restore_job(&cntl, &make_req, &make_res, nullptr);
        ASSERT_EQ(make_res.status().code(), MetaServiceCode::OK);
        std::string restore_job_key = job_restore_tablet_key({instance_id, tablet_id});
        std::string val;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(restore_job_key, &val), TxnErrorCode::TXN_OK);
        std::string restore_job_rs_key = job_restore_rowset_key({instance_id, tablet_id, version});
        ASSERT_EQ(txn->get(restore_job_rs_key, &val), TxnErrorCode::TXN_OK);

        // commit_restore_job
        req.set_tablet_id(tablet_id);
        req.set_action(RestoreJobRequest::COMMIT);
        if (store_version > 0) {
            req.set_store_version(store_version);
        }
        meta_service->commit_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        std::string tablet_key =
                meta_tablet_key({instance_id, table_id, index_id, partition_id, tablet_id});
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(tablet_key, &val), TxnErrorCode::TXN_OK);
        TabletMetaCloudPB saved_tablet_meta;
        ASSERT_TRUE(saved_tablet_meta.ParseFromString(val));
        ASSERT_EQ(saved_tablet_meta.tablet_id(), tablet_id);
        ASSERT_EQ(saved_tablet_meta.schema_version(), 1);
        std::string rs_key = meta_rowset_key({instance_id, tablet_id, version});
        ASSERT_EQ(txn->get(rs_key, &val), TxnErrorCode::TXN_OK);
        RowsetMetaCloudPB saved_rs_meta;
        ASSERT_TRUE(saved_rs_meta.ParseFromString(val));
        ASSERT_EQ(saved_rs_meta.tablet_id(), tablet_id);
        ASSERT_EQ(saved_rs_meta.rowset_id_v2(), rs_meta->rowset_id_v2());
        // check delete bitmap
        {
            TxnErrorCode err = (store_version == 0 || store_version == 1 || store_version == 3)
                                       ? TxnErrorCode::TXN_OK
                                       : TxnErrorCode::TXN_KEY_NOT_FOUND;
            std::string bitmap_key =
                    meta_delete_bitmap_key({instance_id, tablet_id, rs_meta->rowset_id_v2(), 1, 1});
            ASSERT_EQ(txn->get(bitmap_key, &val), err);
            if (err == TxnErrorCode::TXN_OK) {
                ASSERT_EQ(val, "test_bitmap");
            }
            bitmap_key =
                    meta_delete_bitmap_key({instance_id, tablet_id, rs_meta->rowset_id_v2(), 1, 2});
            ASSERT_EQ(txn->get(bitmap_key, &val), err);
            if (err == TxnErrorCode::TXN_OK) {
                ASSERT_EQ(val, "test_bitmap2");
            }
            bitmap_key = meta_delete_bitmap_key(
                    {instance_id, tablet_id, rs_meta->rowset_id_v2() + "2", 1, 1});
            ASSERT_EQ(txn->get(bitmap_key, &val), err);
            if (err == TxnErrorCode::TXN_OK) {
                ASSERT_EQ(val, "test_bitmap3");
            }
        }
        {
            TxnErrorCode err = (store_version == 2 || store_version == 3)
                                       ? TxnErrorCode::TXN_OK
                                       : TxnErrorCode::TXN_KEY_NOT_FOUND;
            std::string bitmap_key = versioned::meta_delete_bitmap_key(
                    {instance_id, tablet_id, rs_meta->rowset_id_v2()});
            ValueBuf val_buf;
            ASSERT_EQ(cloud::blob_get(txn.get(), bitmap_key, &val_buf), err);
            if (err == TxnErrorCode::TXN_OK) {
                DeleteBitmapStoragePB delete_bitmap_storage;
                ASSERT_TRUE(val_buf.to_pb(&delete_bitmap_storage));
                ASSERT_TRUE(delete_bitmap_storage.store_in_fdb());
                auto& delete_bitmap_pb = delete_bitmap_storage.delete_bitmap();
                ASSERT_EQ(delete_bitmap_pb.rowset_ids_size(), 2);
                ASSERT_EQ(delete_bitmap_pb.rowset_ids(0), rs_meta->rowset_id_v2());
                ASSERT_EQ(delete_bitmap_pb.segment_ids(0), 1);
                ASSERT_EQ(delete_bitmap_pb.versions(0), 1);
                ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps(0), "test_bitmap");
                ASSERT_EQ(delete_bitmap_pb.rowset_ids(1), rs_meta->rowset_id_v2());
                ASSERT_EQ(delete_bitmap_pb.segment_ids(1), 2);
                ASSERT_EQ(delete_bitmap_pb.versions(1), 1);
                ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps(1), "test_bitmap2");
            }
            bitmap_key = versioned::meta_delete_bitmap_key(
                    {instance_id, tablet_id, rs_meta->rowset_id_v2() + "2"});
            ASSERT_EQ(cloud::blob_get(txn.get(), bitmap_key, &val_buf), err);
            if (err == TxnErrorCode::TXN_OK) {
                DeleteBitmapStoragePB delete_bitmap_storage;
                ASSERT_TRUE(val_buf.to_pb(&delete_bitmap_storage));
                ASSERT_TRUE(delete_bitmap_storage.store_in_fdb());
                auto& delete_bitmap_pb = delete_bitmap_storage.delete_bitmap();
                ASSERT_EQ(delete_bitmap_pb.rowset_ids_size(), 1);
                ASSERT_EQ(delete_bitmap_pb.rowset_ids(0), rs_meta->rowset_id_v2() + "2");
                ASSERT_EQ(delete_bitmap_pb.segment_ids(0), 1);
                ASSERT_EQ(delete_bitmap_pb.versions(0), 1);
                ASSERT_EQ(delete_bitmap_pb.segment_delete_bitmaps(0), "test_bitmap3");
            }
        }

        // ths restore job key should not be removed, restore job rowset key should be found
        ASSERT_EQ(txn->get(restore_job_key, &val), TxnErrorCode::TXN_OK);
        std::vector<std::pair<std::string, doris::RowsetMetaCloudPB>> restore_job_rs_metas;
        MetaServiceCode code;
        std::string msg;
        scan_restore_job_rowset(txn.get(), instance_id, tablet_id, code, msg,
                                &restore_job_rs_metas);
        ASSERT_EQ(code, MetaServiceCode::OK) << msg;
        ASSERT_EQ(restore_job_rs_metas.size(), 1);
        req.Clear();
        res.Clear();
    }
    // large commit restore job request with 10000 rowset meta
    {
        reset_meta_service();
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        txn->put(meta_tablet_idx_key({instance_id, tablet_id}), tablet_idx_val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

        // prepare restore job
        RestoreJobRequest make_req;
        RestoreJobResponse make_res;
        make_req.set_tablet_id(tablet_id);
        make_req.set_expiration(time(nullptr) + 3600);
        make_req.set_action(RestoreJobRequest::PREPARE);

        auto* tablet_meta = make_req.mutable_tablet_meta();
        tablet_meta->set_table_id(table_id);
        tablet_meta->set_index_id(index_id);
        tablet_meta->set_partition_id(partition_id);
        tablet_meta->set_tablet_id(tablet_id);
        tablet_meta->set_schema_version(1);

        // add 10000 rowset meta
        constexpr int LARGE_ROWSET_COUNT = 10000;
        for (int64_t ver = 1; ver <= LARGE_ROWSET_COUNT; ver++) {
            auto* rs_meta = tablet_meta->add_rs_metas();
            *rs_meta = create_rowset(txn_id, tablet_id, partition_id, ver);
        }

        meta_service->prepare_restore_job(&cntl, &make_req, &make_res, nullptr);
        ASSERT_EQ(make_res.status().code(), MetaServiceCode::OK) << make_res.status().msg();
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        for (int64_t ver = 1; ver <= LARGE_ROWSET_COUNT; ver++) {
            std::string restore_job_rs_key = job_restore_rowset_key({instance_id, tablet_id, ver});
            std::string val;
            ASSERT_EQ(txn->get(restore_job_rs_key, &val), TxnErrorCode::TXN_OK);
            RowsetMetaCloudPB rs_meta_pb;
            ASSERT_TRUE(rs_meta_pb.ParseFromString(val));
            ASSERT_EQ(rs_meta_pb.tablet_id(), tablet_id);
            ASSERT_EQ(rs_meta_pb.start_version(), ver);
            ASSERT_EQ(rs_meta_pb.end_version(), ver);
        }

        // commit_restore_job
        req.set_tablet_id(tablet_id);
        req.set_action(RestoreJobRequest::COMMIT);
        meta_service->commit_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        for (int64_t ver = 1; ver <= LARGE_ROWSET_COUNT; ver++) {
            std::string rs_key = meta_rowset_key({instance_id, tablet_id, ver});
            std::string val;
            ASSERT_EQ(txn->get(rs_key, &val), TxnErrorCode::TXN_OK);
            RowsetMetaCloudPB saved_rs_meta;
            ASSERT_TRUE(saved_rs_meta.ParseFromString(val));
            ASSERT_EQ(saved_rs_meta.tablet_id(), tablet_id);
            ASSERT_EQ(saved_rs_meta.start_version(), ver);
            ASSERT_EQ(saved_rs_meta.end_version(), ver);
        }

        // ths restore job key should not be removed, restore job rowset key should be found
        std::string restore_job_key = job_restore_tablet_key({instance_id, tablet_id});
        std::string val;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(restore_job_key, &val), TxnErrorCode::TXN_OK);
        std::vector<std::pair<std::string, doris::RowsetMetaCloudPB>> restore_job_rs_metas;
        MetaServiceCode code;
        std::string msg;
        scan_restore_job_rowset(txn.get(), instance_id, tablet_id, code, msg,
                                &restore_job_rs_metas);
        ASSERT_EQ(code, MetaServiceCode::OK) << msg;
        ASSERT_EQ(restore_job_rs_metas.size(), 10000);
        req.Clear();
        res.Clear();
    }
    // ------------Test finish restore job------------
    // invalid args finish restore job
    {
        reset_meta_service();
        // empty action
        meta_service->commit_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_TRUE(res.status().msg().find("invalid action") != std::string::npos);
        req.set_action(RestoreJobRequest::COMPLETE);

        // empty tablet_id
        meta_service->finish_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_EQ(res.status().msg(), "empty tablet_id");

        // check key existence
        std::string restore_job_key = job_restore_tablet_key({instance_id, tablet_id});
        std::string val;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(restore_job_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);

        req.Clear();
        res.Clear();
    }
    // finish restore job not exists
    {
        reset_meta_service();
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        txn->put(meta_tablet_idx_key({instance_id, tablet_id}), tablet_idx_val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

        req.set_tablet_id(tablet_id);
        req.set_action(RestoreJobRequest::COMPLETE);
        meta_service->finish_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_EQ(res.status().msg(), "restore job not exists or has been recycled");
        req.Clear();
        res.Clear();
    }
    // finish restore job COMMITTED -> COMPLETED
    {
        reset_meta_service();
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        txn->put(meta_tablet_idx_key({instance_id, tablet_id}), tablet_idx_val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

        // prepare restore job
        RestoreJobRequest make_req;
        RestoreJobResponse make_res;
        make_req.set_tablet_id(tablet_id);
        make_req.set_expiration(time(nullptr) + 3600);
        make_req.set_action(RestoreJobRequest::PREPARE);

        auto* tablet_meta = make_req.mutable_tablet_meta();
        tablet_meta->set_table_id(table_id);
        tablet_meta->set_index_id(index_id);
        tablet_meta->set_partition_id(partition_id);
        tablet_meta->set_tablet_id(tablet_id);
        tablet_meta->set_schema_version(1);
        auto* rs_meta = tablet_meta->add_rs_metas();
        *rs_meta = create_rowset(txn_id, tablet_id, partition_id, version);
        auto* delete_bitmap = tablet_meta->mutable_delete_bitmap();
        delete_bitmap->add_rowset_ids(rs_meta->rowset_id_v2());
        delete_bitmap->add_versions(1);
        delete_bitmap->add_segment_ids(1);
        delete_bitmap->add_segment_delete_bitmaps("test_bitmap");

        meta_service->prepare_restore_job(&cntl, &make_req, &make_res, nullptr);
        ASSERT_EQ(make_res.status().code(), MetaServiceCode::OK);
        std::string restore_job_key = job_restore_tablet_key({instance_id, tablet_id});
        std::string val;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(restore_job_key, &val), TxnErrorCode::TXN_OK);
        std::string restore_job_rs_key = job_restore_rowset_key({instance_id, tablet_id, version});
        ASSERT_EQ(txn->get(restore_job_rs_key, &val), TxnErrorCode::TXN_OK);

        req.set_tablet_id(tablet_id);
        req.set_action(RestoreJobRequest::COMMIT);
        meta_service->commit_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(restore_job_key, &val), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(restore_job_rs_key, &val), TxnErrorCode::TXN_OK);

        // finish_restore_job to COMPLETED
        req.set_tablet_id(tablet_id);
        req.set_action(RestoreJobRequest::COMPLETE);
        meta_service->finish_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();

        // this restore job key should be in COMPLETED state
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(restore_job_key, &val), TxnErrorCode::TXN_OK);
        RestoreJobCloudPB restore_job_pb;
        ASSERT_TRUE(restore_job_pb.ParseFromString(val));
        ASSERT_EQ(restore_job_pb.state(), RestoreJobCloudPB::COMPLETED);
        ASSERT_EQ(restore_job_pb.need_recycle_data(), false);
        std::vector<std::pair<std::string, doris::RowsetMetaCloudPB>> restore_job_rs_metas;
        MetaServiceCode code;
        std::string msg;
        scan_restore_job_rowset(txn.get(), instance_id, tablet_id, code, msg,
                                &restore_job_rs_metas);
        ASSERT_EQ(code, MetaServiceCode::OK) << msg;
        ASSERT_EQ(restore_job_rs_metas.size(), 1);
        req.Clear();
        res.Clear();
    }
    // finish restore job state PREPARED -> DROPPED
    {
        reset_meta_service();
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        txn->put(meta_tablet_idx_key({instance_id, tablet_id}), tablet_idx_val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

        // prepare restore job
        RestoreJobRequest make_req;
        RestoreJobResponse make_res;
        make_req.set_tablet_id(tablet_id);
        make_req.set_expiration(time(nullptr) + 3600);
        make_req.set_action(RestoreJobRequest::PREPARE);

        auto* tablet_meta = make_req.mutable_tablet_meta();
        tablet_meta->set_table_id(table_id);
        tablet_meta->set_index_id(index_id);
        tablet_meta->set_partition_id(partition_id);
        tablet_meta->set_tablet_id(tablet_id);
        tablet_meta->set_schema_version(1);
        auto* rs_meta = tablet_meta->add_rs_metas();
        *rs_meta = create_rowset(txn_id, tablet_id, partition_id, version);

        meta_service->prepare_restore_job(&cntl, &make_req, &make_res, nullptr);
        ASSERT_EQ(make_res.status().code(), MetaServiceCode::OK);
        std::string restore_job_key = job_restore_tablet_key({instance_id, tablet_id});
        std::string val;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(restore_job_key, &val), TxnErrorCode::TXN_OK);
        std::string restore_job_rs_key = job_restore_rowset_key({instance_id, tablet_id, version});
        ASSERT_EQ(txn->get(restore_job_rs_key, &val), TxnErrorCode::TXN_OK);

        // finish_restore_job to DROPPED
        req.set_tablet_id(tablet_id);
        req.set_action(RestoreJobRequest::ABORT);
        meta_service->finish_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();

        // this restore job key should be in DROPPED state
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(restore_job_key, &val), TxnErrorCode::TXN_OK);
        RestoreJobCloudPB restore_job_pb;
        ASSERT_TRUE(restore_job_pb.ParseFromString(val));
        ASSERT_EQ(restore_job_pb.state(), RestoreJobCloudPB::DROPPED);
        ASSERT_EQ(restore_job_pb.need_recycle_data(), true);
        std::vector<std::pair<std::string, doris::RowsetMetaCloudPB>> restore_job_rs_metas;
        MetaServiceCode code;
        std::string msg;
        scan_restore_job_rowset(txn.get(), instance_id, tablet_id, code, msg,
                                &restore_job_rs_metas);
        ASSERT_EQ(code, MetaServiceCode::OK) << msg;
        ASSERT_EQ(restore_job_rs_metas.size(), 1);
        req.Clear();
        res.Clear();
    }
    // finish restore job invalid state PREPARED -> COMPLETED
    {
        reset_meta_service();
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        txn->put(meta_tablet_idx_key({instance_id, tablet_id}), tablet_idx_val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);

        // prepare restore job
        RestoreJobRequest make_req;
        RestoreJobResponse make_res;
        make_req.set_tablet_id(tablet_id);
        make_req.set_expiration(time(nullptr) + 3600);
        make_req.set_action(RestoreJobRequest::PREPARE);

        // set tablet meta
        auto* tablet_meta = make_req.mutable_tablet_meta();
        tablet_meta->set_table_id(table_id);
        tablet_meta->set_index_id(index_id);
        tablet_meta->set_partition_id(partition_id);
        tablet_meta->set_tablet_id(tablet_id);
        tablet_meta->set_schema_version(1);
        auto* rs_meta = tablet_meta->add_rs_metas();
        *rs_meta = create_rowset(txn_id, tablet_id, partition_id, version);

        meta_service->prepare_restore_job(&cntl, &make_req, &make_res, nullptr);
        ASSERT_EQ(make_res.status().code(), MetaServiceCode::OK);

        // finish_restore_job to COMPLETED
        req.set_tablet_id(tablet_id);
        req.set_action(RestoreJobRequest::COMPLETE);
        meta_service->finish_restore_job(&cntl, &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_TRUE(res.status().msg().find("invalid state to complete") != std::string::npos);
    }
}

TEST(MetaServiceTest, SetSnapshotPropertyTest) {
    auto meta_service = get_meta_service();

    // Create a test instance first
    {
        brpc::Controller cntl;
        CreateInstanceRequest req;
        req.set_instance_id("test_snapshot_instance");
        req.set_user_id("test_user");
        req.set_name("test_name");

        auto* sp = SyncPoint::get_instance();
        sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
            auto* ret = try_any_cast<int*>(args[0]);
            *ret = 0;
            auto* key = try_any_cast<std::string*>(args[1]);
            *key = "test";
            auto* key_id = try_any_cast<int64_t*>(args[2]);
            *key_id = 1;
        });
        sp->enable_processing();
        CreateInstanceResponse res;
        meta_service->create_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                      &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        sp->clear_all_call_backs();
        sp->clear_trace();
        sp->disable_processing();
    }

    auto* sp = SyncPoint::get_instance();
    sp->set_call_back("notify_refresh_instance_return",
                      [](auto&& args) { *try_any_cast<bool*>(args.back()) = true; });
    sp->enable_processing();
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
        SyncPoint::get_instance()->disable_processing();
    };

    // Test case 1: Snapshot not ready (SNAPSHOT_SWITCH_DISABLED) - should fail
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::ENABLE_SNAPSHOT)] = "true";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_TRUE(res.status().msg().find("Snapshot is not ready") != std::string::npos);
    }

    // Initialize snapshot switch status to OFF so we can test snapshot functionality
    {
        InstanceKeyInfo key_info {"test_snapshot_instance"};
        std::string key;
        std::string val;
        instance_key(key_info, &key);
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        InstanceInfoPB instance;
        instance.ParseFromString(val);
        instance.set_snapshot_switch_status(SNAPSHOT_SWITCH_OFF);
        instance.set_multi_version_status(MULTI_VERSION_READ_WRITE);
        val = instance.SerializeAsString();
        txn->put(key, val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    }

    // Test case 2: Valid "enabled" property with "true" value (after initialization)
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::ENABLE_SNAPSHOT)] = "true";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // Test case 3: Valid "enabled" property with "false" value
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::ENABLE_SNAPSHOT)] = "false";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // Test case 4: Invalid "enabled" property value
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::ENABLE_SNAPSHOT)] = "invalid";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    }

    // Test case 5: Valid "max_reserved_snapshots" property (minimum value)
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::MAX_RESERVED_SNAPSHOTS)] = "0";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // Test case 6: Valid "max_reserved_snapshots" property (maximum value)
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::MAX_RESERVED_SNAPSHOTS)] = "35";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // Test case 7: Valid "max_reserved_snapshots" property (middle value)
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::MAX_RESERVED_SNAPSHOTS)] = "10";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // Test case 8: Invalid "max_reserved_snapshots" property (negative value)
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::MAX_RESERVED_SNAPSHOTS)] = "-1";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    }

    // Test case 9: Invalid "max_reserved_snapshots" property (too large value)
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::MAX_RESERVED_SNAPSHOTS)] = "36";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    }

    // Test case 10: Invalid "max_reserved_snapshots" property (non-numeric value)
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::MAX_RESERVED_SNAPSHOTS)] = "abc";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    }

    // Test case 11: Valid "snapshot_interval_seconds" property (minimum value)
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::SNAPSHOT_INTERVAL_SECONDS)] = "3600";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // Test case 12: Valid "snapshot_interval_seconds" property (large value)
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::SNAPSHOT_INTERVAL_SECONDS)] = "14400";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // Test case 13: Invalid "snapshot_interval_seconds" property (too small value)
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::SNAPSHOT_INTERVAL_SECONDS)] = "3599";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    }

    // Test case 14: Invalid "snapshot_interval_seconds" property (non-numeric value)
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::SNAPSHOT_INTERVAL_SECONDS)] = "invalid";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    }

    // Test case 15: Unsupported property key
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())["unsupported_property"] = "value";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    }

    // Test case 16: Empty properties map
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        // Don't add any properties - properties map will be empty

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
    }

    // Test case 17: Multiple valid properties in single request
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::ENABLE_SNAPSHOT)] = "true";
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::MAX_RESERVED_SNAPSHOTS)] = "20";
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::SNAPSHOT_INTERVAL_SECONDS)] = "12000";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // Test case 18: Mixed valid and invalid properties (should fail on first invalid)
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::ENABLE_SNAPSHOT)] = "true";
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::MAX_RESERVED_SNAPSHOTS)] = "10";
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::SNAPSHOT_INTERVAL_SECONDS)] = "12000";
        (*req.mutable_properties())["unsupported_property"] = "value";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT) << res.ShortDebugString();
    }

    // Test case 19: Non-existent instance ID
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("non_existent_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::ENABLE_SNAPSHOT)] = "true";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::CLUSTER_NOT_FOUND);
    }
}

TEST(MetaServiceTest, SnapshotConfigLimitsTest) {
    auto meta_service = get_meta_service();

    // Create a test instance first
    {
        brpc::Controller cntl;
        CreateInstanceRequest req;
        req.set_instance_id("test_snapshot_config_instance");
        req.set_user_id("test_user");
        req.set_name("test_name");

        auto* sp = SyncPoint::get_instance();
        sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
            auto* ret = try_any_cast<int*>(args[0]);
            *ret = 0;
            auto* key = try_any_cast<std::string*>(args[1]);
            *key = "test";
            auto* key_id = try_any_cast<int64_t*>(args[2]);
            *key_id = 1;
        });
        sp->enable_processing();
        CreateInstanceResponse res;
        meta_service->create_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                      &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        sp->clear_all_call_backs();
        sp->clear_trace();
        sp->disable_processing();
    }

    auto* sp = SyncPoint::get_instance();
    sp->set_call_back("notify_refresh_instance_return",
                      [](auto&& args) { *try_any_cast<bool*>(args.back()) = true; });
    sp->enable_processing();
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
        SyncPoint::get_instance()->disable_processing();
    };

    // Initialize snapshot switch status to OFF so we can test snapshot functionality
    {
        InstanceKeyInfo key_info {"test_snapshot_config_instance"};
        std::string key;
        std::string val;
        instance_key(key_info, &key);
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        InstanceInfoPB instance;
        instance.ParseFromString(val);
        instance.set_snapshot_switch_status(SNAPSHOT_SWITCH_OFF);
        val = instance.SerializeAsString();
        txn->put(key, val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    }

    {
        brpc::Controller cntl;
        AlterInstanceRequest alter_req;
        AlterInstanceResponse alter_res;
        alter_req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        alter_req.set_instance_id("test_snapshot_config_instance");
        (*alter_req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::ENABLE_SNAPSHOT)] = "true";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &alter_req, &alter_res, nullptr);
        ASSERT_EQ(alter_res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_TRUE(alter_res.status().msg().find("MULTI_VERSION_READ_WRITE") != std::string::npos);
    }

    // Set multi-version status to READ_WRITE
    {
        InstanceKeyInfo key_info {"test_snapshot_config_instance"};
        std::string key;
        std::string val;
        instance_key(key_info, &key);
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        InstanceInfoPB instance;
        instance.ParseFromString(val);
        instance.set_multi_version_status(MULTI_VERSION_READ_WRITE);
        val = instance.SerializeAsString();
        txn->put(key, val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    }

    // Enable snapshot for this instance
    {
        brpc::Controller cntl;
        AlterInstanceRequest alter_req;
        AlterInstanceResponse alter_res;
        alter_req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        alter_req.set_instance_id("test_snapshot_config_instance");
        (*alter_req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::ENABLE_SNAPSHOT)] = "true";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &alter_req, &alter_res, nullptr);
        ASSERT_EQ(alter_res.status().code(), MetaServiceCode::OK);
    }

    // Save original config values
    int32_t orig_min_interval = config::snapshot_min_interval_seconds;
    int32_t orig_max_reserved = config::snapshot_max_reserved_num;

    // Temporarily modify config for testing
    config::snapshot_min_interval_seconds = 1800; // 30 minutes
    config::snapshot_max_reserved_num = 20;

    // Test case 1: interval below minimum limit
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_config_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::SNAPSHOT_INTERVAL_SECONDS)] = "1799"; // Below min limit

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_TRUE(res.status().msg().find("minimum is 1800") != std::string::npos);
    }

    // Test case 2: interval within limits should succeed
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_config_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::SNAPSHOT_INTERVAL_SECONDS)] = "3600"; // Within limits

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // Test case 3: max_reserved_snapshots above maximum limit
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_config_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::MAX_RESERVED_SNAPSHOTS)] = "21"; // Above max limit

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_TRUE(res.status().msg().find("maximum is 20") != std::string::npos);
    }

    // Test case 4: max_reserved_snapshots within limits should succeed
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_config_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::MAX_RESERVED_SNAPSHOTS)] = "10"; // Within limits

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // Test case 5: unknown property should fail
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_snapshot_config_instance");
        (*req.mutable_properties())["unknown_property"] = "value";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_TRUE(res.status().msg().find("unknown snapshot property: unknown_property") !=
                    std::string::npos);
    }

    // Restore original config values
    config::snapshot_min_interval_seconds = orig_min_interval;
    config::snapshot_max_reserved_num = orig_max_reserved;
}

TEST(MetaServiceTest, SnapshotDefaultValuesTest) {
    auto meta_service = get_meta_service();

    // Create a test instance first
    {
        brpc::Controller cntl;
        CreateInstanceRequest req;
        req.set_instance_id("test_instance");
        req.set_user_id("test_user");
        req.set_name("test_name");

        auto* sp = SyncPoint::get_instance();
        sp->set_call_back("encrypt_ak_sk:get_encryption_key", [](auto&& args) {
            auto* ret = try_any_cast<int*>(args[0]);
            *ret = 0;
            auto* key = try_any_cast<std::string*>(args[1]);
            *key = "test";
            auto* key_id = try_any_cast<int64_t*>(args[2]);
            *key_id = 1;
        });
        sp->set_call_back("get_instance_id", [&](auto&& args) {
            auto* ret = try_any_cast_ret<std::string>(args);
            ret->first = "test_instance";
            ret->second = true;
        });
        sp->enable_processing();
        CreateInstanceResponse res;
        meta_service->create_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                      &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        sp->clear_all_call_backs();
        sp->clear_trace();
        sp->disable_processing();
    }

    // Initialize snapshot switch status to OFF so we can test snapshot functionality
    {
        InstanceKeyInfo key_info {"test_instance"};
        std::string key;
        std::string val;
        instance_key(key_info, &key);
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(key, &val), TxnErrorCode::TXN_OK);
        InstanceInfoPB instance;
        instance.ParseFromString(val);
        instance.set_snapshot_switch_status(SNAPSHOT_SWITCH_OFF);
        instance.set_multi_version_status(MULTI_VERSION_READ_WRITE);
        val = instance.SerializeAsString();
        txn->put(key, val);
        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
    }

    // First enable snapshot - should set default values
    {
        brpc::Controller cntl;
        AlterInstanceRequest req;
        AlterInstanceResponse res;
        req.set_op(AlterInstanceRequest::SET_SNAPSHOT_PROPERTY);
        req.set_instance_id("test_instance");
        (*req.mutable_properties())[AlterInstanceRequest_SnapshotProperty_Name(
                AlterInstanceRequest::ENABLE_SNAPSHOT)] = "true";

        meta_service->alter_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                     &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // Verify default values were set by checking instance
    {
        brpc::Controller cntl;
        GetInstanceRequest req;
        GetInstanceResponse res;
        req.set_instance_id("test_instance");
        req.set_cloud_unique_id("test_snapshot_defaults_unique_id");

        meta_service->get_instance(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
                                   &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

        const auto& instance = res.instance();
        ASSERT_EQ(instance.snapshot_switch_status(), SnapshotSwitchStatus::SNAPSHOT_SWITCH_ON);
        ASSERT_EQ(instance.snapshot_interval_seconds(), config::snapshot_min_interval_seconds);
        ASSERT_EQ(instance.max_reserved_snapshot(), 0);
    }
}

TEST(MetaServiceTest, CreateTabletIdempotentAndHandlingError) {
    DORIS_CLOUD_DEFER {
        SyncPoint::get_instance()->clear_all_call_backs();
        SyncPoint::get_instance()->disable_processing();
    };

    size_t case_num = 0;
    auto* sp = SyncPoint::get_instance();
    sp->set_call_back("meta_service_test:get_meta_tablet_key_error", [&case_num](auto&& args) {
        if (++case_num == 3) {
            auto* code = try_any_cast<TxnErrorCode*>(args[0]);
            *code = TxnErrorCode::TXN_INVALID_DATA;
        }
    });
    sp->enable_processing();

    auto meta_service = get_meta_service();
    brpc::Controller cntl;
    CreateTabletsRequest req;
    CreateTabletsResponse res;
    int64_t table_id = 100;
    int64_t index_id = 200;
    int64_t partition_id = 300;
    int64_t tablet_id = 400;
    req.set_db_id(1); // default db_id
    add_tablet(req, table_id, index_id, partition_id, tablet_id);
    // normal create
    meta_service->create_tablets(&cntl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    // idempotent
    meta_service->create_tablets(&cntl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);

    // error handling
    meta_service->create_tablets(&cntl, &req, &res, nullptr);
    ASSERT_EQ(res.status().code(), MetaServiceCode::KV_TXN_GET_ERR);
}

TEST(MetaServiceTest, RowsetVisibleTimeTest) {
    auto meta_service = get_meta_service();
    using namespace std::chrono;
    int64_t txn_id = -1;
    // begin txn
    {
        brpc::Controller cntl;
        BeginTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        TxnInfoPB txn_info_pb;
        txn_info_pb.set_db_id(666);
        txn_info_pb.set_label("test_label");
        txn_info_pb.add_table_ids(1234);
        txn_info_pb.set_timeout_ms(36000);
        req.mutable_txn_info()->CopyFrom(txn_info_pb);
        BeginTxnResponse res;
        meta_service->begin_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
        txn_id = res.txn_id();
    }

    // mock rowset and tablet
    int64_t tablet_id_base = 1103;
    for (int i = 0; i < 5; ++i) {
        create_tablet(meta_service.get(), 1234, 1235, 1236, tablet_id_base + i);
        auto tmp_rowset = create_rowset(txn_id, tablet_id_base + i);
        CreateRowsetResponse res;
        commit_rowset(meta_service.get(), tmp_rowset, res);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }
    {
        brpc::Controller cntl;
        CommitTxnRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_db_id(666);
        req.set_txn_id(txn_id);
        CommitTxnResponse res;
        meta_service->commit_txn(reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req,
                                 &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    for (int i = 0; i < 5; ++i) {
        int64_t tablet_id = tablet_id_base + i;
        int64_t ver = 2;
        std::string rowset_key = meta_rowset_key({mock_instance, tablet_id, ver});
        std::string val;
        std::unique_ptr<Transaction> txn;
        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
        ASSERT_EQ(txn->get(rowset_key, &val), TxnErrorCode::TXN_OK);
        RowsetMetaCloudPB rowset_pb;
        ASSERT_TRUE(rowset_pb.ParseFromString(val));
        ASSERT_TRUE(rowset_pb.has_visible_ts_ms());
        std::cout << rowset_pb.visible_ts_ms() << "\n";
        ASSERT_GT(rowset_pb.visible_ts_ms(), 0);
        auto visible_tp = time_point<system_clock>(milliseconds(rowset_pb.visible_ts_ms()));
        std::time_t visible_time = system_clock::to_time_t(visible_tp);
        std::cout << "visible time: "
                  << std::put_time(std::localtime(&visible_time), "%Y%m%d %H:%M:%S") << "\n";
    }
}

TEST(MetaServiceTest, UpdatePackedFileInfoTest) {
    auto meta_service = get_meta_service();

    // case: normal update merge file info - success case
    {
        brpc::Controller cntl;
        UpdatePackedFileInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_packed_file_path("/path/to/packed/file1");

        // Set packed file info
        auto* packed_info = req.mutable_packed_file_info();
        packed_info->set_ref_cnt(5);
        packed_info->set_total_slice_num(10);
        packed_info->set_total_slice_bytes(1024000);
        packed_info->set_remaining_slice_bytes(819200);
        packed_info->set_created_at_sec(1666666666);
        packed_info->set_corrected(false);
        packed_info->set_state(PackedFileInfoPB::NORMAL);

        // Add small files
        auto* small_file = packed_info->add_slices();
        small_file->set_path("/small/file1.txt");
        small_file->set_offset(0);
        small_file->set_size(512);
        small_file->set_deleted(false);

        small_file = packed_info->add_slices();
        small_file->set_path("/small/file2.txt");
        small_file->set_offset(512);
        small_file->set_size(1024);
        small_file->set_deleted(false);

        UpdatePackedFileInfoResponse res;
        meta_service->update_packed_file_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }

    // case: missing cloud_unique_id - should fail
    {
        brpc::Controller cntl;
        UpdatePackedFileInfoRequest req;
        // req.set_cloud_unique_id("test_cloud_unique_id"); // missing cloud_unique_id
        req.set_packed_file_path("/path/to/packed/file");

        auto* packed_info = req.mutable_packed_file_info();
        packed_info->set_ref_cnt(1);
        packed_info->set_total_slice_num(1);

        UpdatePackedFileInfoResponse res;
        meta_service->update_packed_file_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_TRUE(res.status().msg().find("cloud_unique_id is required") != std::string::npos);
    }

    // case: empty cloud_unique_id - should fail
    {
        brpc::Controller cntl;
        UpdatePackedFileInfoRequest req;
        req.set_cloud_unique_id(""); // empty cloud_unique_id
        req.set_packed_file_path("/path/to/merged/file");

        auto* merge_info = req.mutable_packed_file_info();
        merge_info->set_ref_cnt(1);

        UpdatePackedFileInfoResponse res;
        meta_service->update_packed_file_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_TRUE(res.status().msg().find("cloud_unique_id is required") != std::string::npos);
    }

    // case: missing packed_file_path - should fail
    {
        brpc::Controller cntl;
        UpdatePackedFileInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        // req.set_packed_file_path("/path/to/merged/file"); // missing packed_file_path

        auto* merge_info = req.mutable_packed_file_info();
        merge_info->set_ref_cnt(1);

        UpdatePackedFileInfoResponse res;
        meta_service->update_packed_file_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_TRUE(res.status().msg().find("packed_file_path is required") != std::string::npos);
    }

    // case: empty packed_file_path - should fail
    {
        brpc::Controller cntl;
        UpdatePackedFileInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_packed_file_path(""); // empty packed_file_path

        auto* merge_info = req.mutable_packed_file_info();
        merge_info->set_ref_cnt(1);

        UpdatePackedFileInfoResponse res;
        meta_service->update_packed_file_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_TRUE(res.status().msg().find("packed_file_path is required") != std::string::npos);
    }

    // case: missing packed_file_info - should fail
    {
        brpc::Controller cntl;
        UpdatePackedFileInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id");
        req.set_packed_file_path("/path/to/merged/file");
        // No packed_file_info set

        UpdatePackedFileInfoResponse res;
        meta_service->update_packed_file_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT);
        ASSERT_TRUE(res.status().msg().find("packed_file_info is required") != std::string::npos);
    }

    // case: test multiple operations - success case
    {
        brpc::Controller cntl;
        UpdatePackedFileInfoRequest req;
        req.set_cloud_unique_id("test_cloud_unique_id_multi");
        req.set_packed_file_path("/path/to/merged/file_multi");

        // Set merge file info with multiple small files
        auto* merge_info = req.mutable_packed_file_info();
        merge_info->set_ref_cnt(3);
        merge_info->set_total_slice_num(5);
        merge_info->set_total_slice_bytes(2048000);
        merge_info->set_remaining_slice_bytes(1638400);
        merge_info->set_created_at_sec(1666666777);
        merge_info->set_corrected(true);
        merge_info->set_state(PackedFileInfoPB::NORMAL);

        // Add multiple small files
        for (int i = 1; i <= 5; ++i) {
            auto* small_file = merge_info->add_slices();
            small_file->set_path("/small/file" + std::to_string(i) + ".txt");
            small_file->set_offset(i * 1000);
            small_file->set_size(1000);
            small_file->set_deleted(i > 3); // Mark some as deleted
        }

        UpdatePackedFileInfoResponse res;
        meta_service->update_packed_file_info(
                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, &res, nullptr);
        ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
    }
}

} // namespace doris::cloud
